@contextstream/mcp-server 0.4.19 → 0.4.21
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 +448 -53
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7482,6 +7482,7 @@ var CONTEXTSTREAM_TOOL_NAMES = [
|
|
|
7482
7482
|
"session_init",
|
|
7483
7483
|
"context_smart",
|
|
7484
7484
|
"context_feedback",
|
|
7485
|
+
"generate_rules",
|
|
7485
7486
|
// Consolidated domain tools (v0.4.x default)
|
|
7486
7487
|
"search",
|
|
7487
7488
|
// Modes: semantic, hybrid, keyword, pattern
|
|
@@ -7518,7 +7519,7 @@ Rules Version: ${RULES_VERSION}
|
|
|
7518
7519
|
|
|
7519
7520
|
| Message | What to Call |
|
|
7520
7521
|
|---------|--------------|
|
|
7521
|
-
| **1st message** | \`session_init(folder_path="...", context_hint="<user's message>")\` |
|
|
7522
|
+
| **1st message** | \`session_init(folder_path="...", context_hint="<user's message>")\`, then \`context_smart(user_message="<user's message>", format="minified", max_tokens=400)\` |
|
|
7522
7523
|
| **2nd+ messages** | \`context_smart(user_message="<user's message>", format="minified", max_tokens=400)\` |
|
|
7523
7524
|
| **Before risky/non-trivial work** | \`session(action="get_lessons", query="<topic>")\` |
|
|
7524
7525
|
| **After completing task** | \`session(action="capture", event_type="decision", ...)\` - MUST capture |
|
|
@@ -7527,7 +7528,9 @@ Rules Version: ${RULES_VERSION}
|
|
|
7527
7528
|
|
|
7528
7529
|
**NO EXCEPTIONS.** Do not skip even if you think you have enough context.
|
|
7529
7530
|
|
|
7530
|
-
**
|
|
7531
|
+
**First message rule:** After \`session_init\`, always call \`context_smart\` before any other tool or response.
|
|
7532
|
+
|
|
7533
|
+
**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
7534
|
|
|
7532
7535
|
**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
7536
|
|
|
@@ -7539,7 +7542,7 @@ v0.4.x consolidates ~58 individual tools into ~11 domain tools with action/mode
|
|
|
7539
7542
|
|
|
7540
7543
|
### Standalone Tools (Always Call)
|
|
7541
7544
|
- **\`session_init\`** - Initialize session with workspace detection + context
|
|
7542
|
-
- **\`context_smart\`** - Semantic search for relevant context (CALL EVERY MESSAGE)
|
|
7545
|
+
- **\`context_smart\`** - Semantic search for relevant context (CALL EVERY MESSAGE, including immediately after \`session_init\`)
|
|
7543
7546
|
|
|
7544
7547
|
### Domain Tools (Use action/mode parameter)
|
|
7545
7548
|
|
|
@@ -7588,7 +7591,7 @@ If context still feels missing, use \`session(action="recall", query="...")\` fo
|
|
|
7588
7591
|
|
|
7589
7592
|
### Rules Update Notices
|
|
7590
7593
|
|
|
7591
|
-
- If you see **[RULES_NOTICE]**, update rules via \`
|
|
7594
|
+
- If you see **[RULES_NOTICE]**, update rules via \`generate_rules()\` (or rerun setup).
|
|
7592
7595
|
- If you see **[VERSION_NOTICE]**, tell the user to update MCP using the provided command.
|
|
7593
7596
|
|
|
7594
7597
|
---
|
|
@@ -7621,9 +7624,9 @@ Only after this preflight, proceed with search/analysis below.
|
|
|
7621
7624
|
2. \`search(mode="hybrid", query="...")\` or \`search(mode="keyword", query="<filename>")\`
|
|
7622
7625
|
3. \`project(action="files")\` - file tree/list (only when needed)
|
|
7623
7626
|
4. \`graph(action="dependencies", ...)\` - code structure
|
|
7624
|
-
5. Local repo scans (rg/ls/find) - only if ContextStream returns no results or
|
|
7627
|
+
5. Local repo scans (rg/ls/find) - only if ContextStream returns no results, errors, or the user explicitly asks
|
|
7625
7628
|
|
|
7626
|
-
|
|
7629
|
+
If ContextStream returns results, stop and use them. Do NOT use local Search/Explore/Read unless you need exact code edits.
|
|
7627
7630
|
|
|
7628
7631
|
**Code Analysis:**
|
|
7629
7632
|
- Dependencies: \`graph(action="dependencies", file_path="...")\`
|
|
@@ -7709,13 +7712,13 @@ Rules Version: ${RULES_VERSION}
|
|
|
7709
7712
|
|
|
7710
7713
|
| Message | What to Call |
|
|
7711
7714
|
|---------|--------------|
|
|
7712
|
-
| **1st message** | \`session_init(folder_path="<cwd>", context_hint="<user_message>")\` |
|
|
7715
|
+
| **1st message** | \`session_init(folder_path="<cwd>", context_hint="<user_message>")\`, then \`context_smart(user_message="<user_message>", format="minified", max_tokens=400)\` |
|
|
7713
7716
|
| **2nd+ messages** | \`context_smart(user_message="<user_message>", format="minified", max_tokens=400)\` |
|
|
7714
7717
|
| **Capture decisions** | \`session(action="capture", event_type="decision", title="...", content="...")\` |
|
|
7715
7718
|
| **Before risky work** | \`session(action="get_lessons", query="<topic>")\` |
|
|
7716
7719
|
| **On user frustration** | \`session(action="capture_lesson", title="...", trigger="...", impact="...", prevention="...")\` |
|
|
7717
7720
|
|
|
7718
|
-
**Context Pack (Pro+):** If enabled, use \`context_smart(..., mode="pack", distill=true)\` for code/file queries. If unavailable, omit \`mode
|
|
7721
|
+
**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
7722
|
|
|
7720
7723
|
**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
7724
|
|
|
@@ -7734,13 +7737,14 @@ Rules Version: ${RULES_VERSION}
|
|
|
7734
7737
|
|
|
7735
7738
|
### Behavior Rules
|
|
7736
7739
|
|
|
7737
|
-
- **First message**:
|
|
7740
|
+
- **First message**: Call \`session_init\` with context_hint, then call \`context_smart\` before any other tool or response
|
|
7738
7741
|
- **Every message after**: Always call \`context_smart\` BEFORE responding (semantic search for relevant context)
|
|
7739
7742
|
- **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
7743
|
- **For discovery**: Use \`session(action="smart_search")\` or \`search(mode="hybrid")\` before any local repo scans
|
|
7741
7744
|
- **For file/function/config lookups**: Use \`search\`/\`graph\` first; only fall back to rg/ls/find if ContextStream returns no results
|
|
7745
|
+
- **If ContextStream returns results**: Do NOT use local Search/Explore/Read; only open specific files when needed for exact edits
|
|
7742
7746
|
- **For code analysis**: Use \`graph(action="dependencies")\` or \`graph(action="impact")\` for call/dependency analysis
|
|
7743
|
-
- **On [RULES_NOTICE]**: Use \`
|
|
7747
|
+
- **On [RULES_NOTICE]**: Use \`generate_rules()\` to update rules
|
|
7744
7748
|
- **After completing work**: Always capture decisions/insights with \`session(action="capture")\`
|
|
7745
7749
|
- **On mistakes/corrections**: Immediately capture lessons with \`session(action="capture_lesson")\`
|
|
7746
7750
|
|
|
@@ -8054,6 +8058,7 @@ var DEFAULT_PARAM_DESCRIPTIONS = {
|
|
|
8054
8058
|
recurrence: "Recurrence pattern (daily, weekly, monthly).",
|
|
8055
8059
|
keywords: "Keywords for matching.",
|
|
8056
8060
|
overwrite: "Allow overwriting existing files on disk.",
|
|
8061
|
+
overwrite_existing: "Allow overwriting existing rule files (ContextStream block only).",
|
|
8057
8062
|
write_to_disk: "Write ingested files to disk before indexing.",
|
|
8058
8063
|
await_indexing: "Wait for indexing to finish before returning.",
|
|
8059
8064
|
auto_index: "Automatically index on creation.",
|
|
@@ -8172,12 +8177,12 @@ function getRulesNotice(folderPath, clientName) {
|
|
|
8172
8177
|
const candidates = resolveRulesCandidatePaths(folderPath, editorKey);
|
|
8173
8178
|
const existing = candidates.filter((filePath) => fs3.existsSync(filePath));
|
|
8174
8179
|
if (existing.length === 0) {
|
|
8175
|
-
const updateCommand2 =
|
|
8180
|
+
const updateCommand2 = "generate_rules()";
|
|
8176
8181
|
const notice2 = {
|
|
8177
8182
|
status: "missing",
|
|
8178
8183
|
latest: RULES_VERSION,
|
|
8179
8184
|
files_checked: candidates,
|
|
8180
|
-
update_tool: "
|
|
8185
|
+
update_tool: "generate_rules",
|
|
8181
8186
|
update_args: {
|
|
8182
8187
|
...folderPath ? { folder_path: folderPath } : {},
|
|
8183
8188
|
editors: editorKey ? [editorKey] : ["all"]
|
|
@@ -8211,7 +8216,7 @@ function getRulesNotice(folderPath, clientName) {
|
|
|
8211
8216
|
return null;
|
|
8212
8217
|
}
|
|
8213
8218
|
const current = versions.sort(compareVersions2).at(-1);
|
|
8214
|
-
const updateCommand =
|
|
8219
|
+
const updateCommand = "generate_rules()";
|
|
8215
8220
|
const notice = {
|
|
8216
8221
|
status: filesOutdated.length > 0 ? "behind" : "unknown",
|
|
8217
8222
|
current,
|
|
@@ -8219,7 +8224,7 @@ function getRulesNotice(folderPath, clientName) {
|
|
|
8219
8224
|
files_checked: existing,
|
|
8220
8225
|
...filesOutdated.length > 0 ? { files_outdated: filesOutdated } : {},
|
|
8221
8226
|
...filesMissingVersion.length > 0 ? { files_missing_version: filesMissingVersion } : {},
|
|
8222
|
-
update_tool: "
|
|
8227
|
+
update_tool: "generate_rules",
|
|
8223
8228
|
update_args: {
|
|
8224
8229
|
...folderPath ? { folder_path: folderPath } : {},
|
|
8225
8230
|
editors: editorKey ? [editorKey] : ["all"]
|
|
@@ -8261,6 +8266,19 @@ var LEGACY_CONTEXTSTREAM_ALLOWED_HEADINGS = [
|
|
|
8261
8266
|
"plans & tasks",
|
|
8262
8267
|
"complete action reference"
|
|
8263
8268
|
];
|
|
8269
|
+
var CONTEXTSTREAM_PREAMBLE_PATTERNS = [
|
|
8270
|
+
/^#\s+workspace:/i,
|
|
8271
|
+
/^#\s+project:/i,
|
|
8272
|
+
/^#\s+workspace id:/i,
|
|
8273
|
+
/^#\s+codex cli instructions$/i,
|
|
8274
|
+
/^#\s+claude code instructions$/i,
|
|
8275
|
+
/^#\s+cursor rules$/i,
|
|
8276
|
+
/^#\s+windsurf rules$/i,
|
|
8277
|
+
/^#\s+cline rules$/i,
|
|
8278
|
+
/^#\s+kilo code rules$/i,
|
|
8279
|
+
/^#\s+roo code rules$/i,
|
|
8280
|
+
/^#\s+aider configuration$/i
|
|
8281
|
+
];
|
|
8264
8282
|
function wrapWithMarkers(content) {
|
|
8265
8283
|
return `${CONTEXTSTREAM_START_MARKER}
|
|
8266
8284
|
${content.trim()}
|
|
@@ -8281,6 +8299,96 @@ function isLegacyContextStreamRules(content) {
|
|
|
8281
8299
|
}
|
|
8282
8300
|
return hasHeading;
|
|
8283
8301
|
}
|
|
8302
|
+
function isContextStreamPreamble(line) {
|
|
8303
|
+
const trimmed = line.trim();
|
|
8304
|
+
if (!trimmed) return false;
|
|
8305
|
+
return CONTEXTSTREAM_PREAMBLE_PATTERNS.some((pattern) => pattern.test(trimmed));
|
|
8306
|
+
}
|
|
8307
|
+
function findContextStreamHeading(lines) {
|
|
8308
|
+
const headingRegex = /^(#{1,6})\s+(.+)$/;
|
|
8309
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
8310
|
+
const match = headingRegex.exec(lines[i]);
|
|
8311
|
+
if (!match) continue;
|
|
8312
|
+
if (match[2].toLowerCase().includes("contextstream")) {
|
|
8313
|
+
return { index: i, level: match[1].length };
|
|
8314
|
+
}
|
|
8315
|
+
}
|
|
8316
|
+
return null;
|
|
8317
|
+
}
|
|
8318
|
+
function findSectionEnd(lines, startLine, level) {
|
|
8319
|
+
const headingRegex = /^(#{1,6})\s+(.+)$/;
|
|
8320
|
+
for (let i = startLine + 1; i < lines.length; i += 1) {
|
|
8321
|
+
const match = headingRegex.exec(lines[i]);
|
|
8322
|
+
if (!match) continue;
|
|
8323
|
+
if (match[1].length <= level) return i;
|
|
8324
|
+
}
|
|
8325
|
+
return lines.length;
|
|
8326
|
+
}
|
|
8327
|
+
function extractContextStreamBlock(content) {
|
|
8328
|
+
const lines = content.split(/\r?\n/);
|
|
8329
|
+
const heading = findContextStreamHeading(lines);
|
|
8330
|
+
if (!heading) return content.trim();
|
|
8331
|
+
const endLine = findSectionEnd(lines, heading.index, heading.level);
|
|
8332
|
+
return lines.slice(heading.index, endLine).join("\n").trim();
|
|
8333
|
+
}
|
|
8334
|
+
function findLegacyContextStreamSection(content) {
|
|
8335
|
+
const lines = content.split(/\r?\n/);
|
|
8336
|
+
const heading = findContextStreamHeading(lines);
|
|
8337
|
+
if (!heading) return null;
|
|
8338
|
+
let startLine = heading.index;
|
|
8339
|
+
for (let i = heading.index - 1; i >= 0; i -= 1) {
|
|
8340
|
+
const line = lines[i];
|
|
8341
|
+
if (!line.trim()) {
|
|
8342
|
+
startLine = i;
|
|
8343
|
+
continue;
|
|
8344
|
+
}
|
|
8345
|
+
if (isContextStreamPreamble(line)) {
|
|
8346
|
+
startLine = i;
|
|
8347
|
+
continue;
|
|
8348
|
+
}
|
|
8349
|
+
break;
|
|
8350
|
+
}
|
|
8351
|
+
const endLine = findSectionEnd(lines, heading.index, heading.level);
|
|
8352
|
+
return { startLine, endLine, contextLine: heading.index };
|
|
8353
|
+
}
|
|
8354
|
+
function blockHasPreamble(block) {
|
|
8355
|
+
const lines = block.split(/\r?\n/);
|
|
8356
|
+
for (const line of lines) {
|
|
8357
|
+
const trimmed = line.trim();
|
|
8358
|
+
if (!trimmed) continue;
|
|
8359
|
+
if (isContextStreamPreamble(trimmed)) return true;
|
|
8360
|
+
if (/^#{1,6}\s+/.test(trimmed)) return false;
|
|
8361
|
+
}
|
|
8362
|
+
return false;
|
|
8363
|
+
}
|
|
8364
|
+
function replaceContextStreamBlock(existing, content) {
|
|
8365
|
+
const fullWrapped = wrapWithMarkers(content);
|
|
8366
|
+
const blockWrapped = wrapWithMarkers(extractContextStreamBlock(content));
|
|
8367
|
+
const startIdx = existing.indexOf(CONTEXTSTREAM_START_MARKER);
|
|
8368
|
+
const endIdx = existing.indexOf(CONTEXTSTREAM_END_MARKER);
|
|
8369
|
+
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
8370
|
+
const existingBlock = existing.slice(startIdx + CONTEXTSTREAM_START_MARKER.length, endIdx);
|
|
8371
|
+
const replacement = blockHasPreamble(existingBlock) ? fullWrapped : blockWrapped;
|
|
8372
|
+
const before = existing.substring(0, startIdx).trimEnd();
|
|
8373
|
+
const after = existing.substring(endIdx + CONTEXTSTREAM_END_MARKER.length).trimStart();
|
|
8374
|
+
const merged = [before, replacement, after].filter((part) => part.length > 0).join("\n\n");
|
|
8375
|
+
return { content: merged.trim() + "\n", status: "updated" };
|
|
8376
|
+
}
|
|
8377
|
+
const legacy = findLegacyContextStreamSection(existing);
|
|
8378
|
+
if (legacy) {
|
|
8379
|
+
const lines = existing.split(/\r?\n/);
|
|
8380
|
+
const before = lines.slice(0, legacy.startLine).join("\n").trimEnd();
|
|
8381
|
+
const after = lines.slice(legacy.endLine).join("\n").trimStart();
|
|
8382
|
+
const replacement = legacy.startLine < legacy.contextLine ? fullWrapped : blockWrapped;
|
|
8383
|
+
const merged = [before, replacement, after].filter((part) => part.length > 0).join("\n\n");
|
|
8384
|
+
return { content: merged.trim() + "\n", status: "updated" };
|
|
8385
|
+
}
|
|
8386
|
+
if (isLegacyContextStreamRules(existing)) {
|
|
8387
|
+
return { content: fullWrapped + "\n", status: "updated" };
|
|
8388
|
+
}
|
|
8389
|
+
const appended = existing.trimEnd() + "\n\n" + blockWrapped + "\n";
|
|
8390
|
+
return { content: appended, status: "appended" };
|
|
8391
|
+
}
|
|
8284
8392
|
async function upsertRuleFile(filePath, content) {
|
|
8285
8393
|
await fs3.promises.mkdir(path4.dirname(filePath), { recursive: true });
|
|
8286
8394
|
const wrappedContent = wrapWithMarkers(content);
|
|
@@ -8293,22 +8401,13 @@ async function upsertRuleFile(filePath, content) {
|
|
|
8293
8401
|
await fs3.promises.writeFile(filePath, wrappedContent + "\n", "utf8");
|
|
8294
8402
|
return "created";
|
|
8295
8403
|
}
|
|
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)) {
|
|
8404
|
+
if (!existing.trim()) {
|
|
8306
8405
|
await fs3.promises.writeFile(filePath, wrappedContent + "\n", "utf8");
|
|
8307
8406
|
return "updated";
|
|
8308
8407
|
}
|
|
8309
|
-
const
|
|
8310
|
-
await fs3.promises.writeFile(filePath,
|
|
8311
|
-
return
|
|
8408
|
+
const replaced = replaceContextStreamBlock(existing, content);
|
|
8409
|
+
await fs3.promises.writeFile(filePath, replaced.content, "utf8");
|
|
8410
|
+
return replaced.status;
|
|
8312
8411
|
}
|
|
8313
8412
|
async function writeEditorRules(options) {
|
|
8314
8413
|
const editors = options.editors && options.editors.length > 0 ? options.editors : getAvailableEditors();
|
|
@@ -8326,6 +8425,10 @@ async function writeEditorRules(options) {
|
|
|
8326
8425
|
continue;
|
|
8327
8426
|
}
|
|
8328
8427
|
const filePath = path4.join(options.folderPath, rule.filename);
|
|
8428
|
+
if (fs3.existsSync(filePath) && !options.overwriteExisting) {
|
|
8429
|
+
results.push({ editor, filename: rule.filename, status: "skipped (exists)" });
|
|
8430
|
+
continue;
|
|
8431
|
+
}
|
|
8329
8432
|
try {
|
|
8330
8433
|
const status = await upsertRuleFile(filePath, rule.content);
|
|
8331
8434
|
results.push({ editor, filename: rule.filename, status });
|
|
@@ -8344,6 +8447,55 @@ async function writeEditorRules(options) {
|
|
|
8344
8447
|
}
|
|
8345
8448
|
return results;
|
|
8346
8449
|
}
|
|
8450
|
+
function listGlobalRuleTargets(editors) {
|
|
8451
|
+
const targets = [];
|
|
8452
|
+
for (const editor of editors) {
|
|
8453
|
+
const globalPaths = RULES_GLOBAL_FILES[editor];
|
|
8454
|
+
if (!globalPaths || globalPaths.length === 0) {
|
|
8455
|
+
continue;
|
|
8456
|
+
}
|
|
8457
|
+
for (const filePath of globalPaths) {
|
|
8458
|
+
targets.push({ editor, filePath });
|
|
8459
|
+
}
|
|
8460
|
+
}
|
|
8461
|
+
return targets;
|
|
8462
|
+
}
|
|
8463
|
+
async function writeGlobalRules(options) {
|
|
8464
|
+
const results = [];
|
|
8465
|
+
for (const editor of options.editors) {
|
|
8466
|
+
const rule = generateRuleContent(editor, {
|
|
8467
|
+
mode: options.mode
|
|
8468
|
+
});
|
|
8469
|
+
if (!rule) {
|
|
8470
|
+
results.push({ editor, filename: "", status: "unknown editor", scope: "global" });
|
|
8471
|
+
continue;
|
|
8472
|
+
}
|
|
8473
|
+
const globalPaths = RULES_GLOBAL_FILES[editor] ?? [];
|
|
8474
|
+
if (globalPaths.length === 0) {
|
|
8475
|
+
results.push({ editor, filename: rule.filename, status: "skipped (no global path)", scope: "global" });
|
|
8476
|
+
continue;
|
|
8477
|
+
}
|
|
8478
|
+
for (const filePath of globalPaths) {
|
|
8479
|
+
if (fs3.existsSync(filePath) && !options.overwriteExisting) {
|
|
8480
|
+
results.push({ editor, filename: filePath, status: "skipped (exists)", scope: "global" });
|
|
8481
|
+
continue;
|
|
8482
|
+
}
|
|
8483
|
+
try {
|
|
8484
|
+
const status = await upsertRuleFile(filePath, rule.content);
|
|
8485
|
+
results.push({ editor, filename: filePath, status, scope: "global" });
|
|
8486
|
+
} catch (err) {
|
|
8487
|
+
results.push({
|
|
8488
|
+
editor,
|
|
8489
|
+
filename: filePath,
|
|
8490
|
+
status: `error: ${err.message}`,
|
|
8491
|
+
scope: "global"
|
|
8492
|
+
});
|
|
8493
|
+
}
|
|
8494
|
+
}
|
|
8495
|
+
}
|
|
8496
|
+
rulesNoticeCache.clear();
|
|
8497
|
+
return results;
|
|
8498
|
+
}
|
|
8347
8499
|
var WRITE_VERBS = /* @__PURE__ */ new Set([
|
|
8348
8500
|
"create",
|
|
8349
8501
|
"update",
|
|
@@ -8545,6 +8697,7 @@ var LIGHT_TOOLSET = /* @__PURE__ */ new Set([
|
|
|
8545
8697
|
"session_delta",
|
|
8546
8698
|
// Setup and configuration (3)
|
|
8547
8699
|
"generate_editor_rules",
|
|
8700
|
+
"generate_rules",
|
|
8548
8701
|
"workspace_associate",
|
|
8549
8702
|
"workspace_bootstrap",
|
|
8550
8703
|
// Project management (5)
|
|
@@ -8590,6 +8743,7 @@ var STANDARD_TOOLSET = /* @__PURE__ */ new Set([
|
|
|
8590
8743
|
"session_delta",
|
|
8591
8744
|
// Setup and configuration (3)
|
|
8592
8745
|
"generate_editor_rules",
|
|
8746
|
+
"generate_rules",
|
|
8593
8747
|
"workspace_associate",
|
|
8594
8748
|
"workspace_bootstrap",
|
|
8595
8749
|
// Workspace management (2)
|
|
@@ -8798,7 +8952,8 @@ var TOOL_BUNDLES = {
|
|
|
8798
8952
|
"session_compress",
|
|
8799
8953
|
"session_delta",
|
|
8800
8954
|
"decision_trace",
|
|
8801
|
-
"generate_editor_rules"
|
|
8955
|
+
"generate_editor_rules",
|
|
8956
|
+
"generate_rules"
|
|
8802
8957
|
]),
|
|
8803
8958
|
// Memory bundle (~12 tools) - full memory CRUD operations
|
|
8804
8959
|
memory: /* @__PURE__ */ new Set([
|
|
@@ -8933,7 +9088,7 @@ function inferOperationCategory(name) {
|
|
|
8933
9088
|
if (name.startsWith("reminder")) return "Reminders";
|
|
8934
9089
|
if (name.startsWith("slack_") || name.startsWith("github_") || name.startsWith("integration")) return "Integrations";
|
|
8935
9090
|
if (name.startsWith("ai_")) return "AI";
|
|
8936
|
-
if (name === "auth_me" || name === "mcp_server_version" || name === "generate_editor_rules") return "Utility";
|
|
9091
|
+
if (name === "auth_me" || name === "mcp_server_version" || name === "generate_editor_rules" || name === "generate_rules") return "Utility";
|
|
8937
9092
|
if (name === "tools_enable_bundle" || name === "contextstream" || name === "contextstream_help") return "Meta";
|
|
8938
9093
|
return "Other";
|
|
8939
9094
|
}
|
|
@@ -9007,6 +9162,8 @@ var CONSOLIDATED_TOOLS = /* @__PURE__ */ new Set([
|
|
|
9007
9162
|
// Standalone - complex initialization
|
|
9008
9163
|
"context_smart",
|
|
9009
9164
|
// Standalone - called every message
|
|
9165
|
+
"generate_rules",
|
|
9166
|
+
// Standalone - rule generation helper
|
|
9010
9167
|
"search",
|
|
9011
9168
|
// Consolidates search_semantic, search_hybrid, search_keyword, search_pattern
|
|
9012
9169
|
"session",
|
|
@@ -10039,7 +10196,8 @@ Access: Free`,
|
|
|
10039
10196
|
description: external_exports.string().optional().describe("Project description"),
|
|
10040
10197
|
workspace_id: external_exports.string().uuid().optional().describe("Workspace ID (uses current session workspace if not provided)"),
|
|
10041
10198
|
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)")
|
|
10199
|
+
generate_editor_rules: external_exports.boolean().optional().describe("Generate AI editor rules in folder_path (requires folder_path)"),
|
|
10200
|
+
overwrite_existing: external_exports.boolean().optional().describe("Allow overwriting existing rule files when generating editor rules")
|
|
10043
10201
|
})
|
|
10044
10202
|
},
|
|
10045
10203
|
async (input) => {
|
|
@@ -10054,6 +10212,7 @@ Access: Free`,
|
|
|
10054
10212
|
});
|
|
10055
10213
|
const projectData = result;
|
|
10056
10214
|
let rulesGenerated = [];
|
|
10215
|
+
let rulesSkipped = [];
|
|
10057
10216
|
if (input.folder_path && projectData.id) {
|
|
10058
10217
|
try {
|
|
10059
10218
|
const configDir = path4.join(input.folder_path, ".contextstream");
|
|
@@ -10072,9 +10231,11 @@ Access: Free`,
|
|
|
10072
10231
|
folderPath: input.folder_path,
|
|
10073
10232
|
editors: getAvailableEditors(),
|
|
10074
10233
|
workspaceId,
|
|
10075
|
-
projectName: input.name
|
|
10234
|
+
projectName: input.name,
|
|
10235
|
+
overwriteExisting: input.overwrite_existing
|
|
10076
10236
|
});
|
|
10077
10237
|
rulesGenerated = ruleResults.filter((r) => r.status === "created" || r.status === "updated" || r.status === "appended").map((r) => r.status === "created" ? r.filename : `${r.filename} (${r.status})`);
|
|
10238
|
+
rulesSkipped = ruleResults.filter((r) => r.status.startsWith("skipped")).map((r) => r.filename);
|
|
10078
10239
|
}
|
|
10079
10240
|
} catch (err) {
|
|
10080
10241
|
console.error("[ContextStream] Failed to write project config:", err);
|
|
@@ -10084,7 +10245,8 @@ Access: Free`,
|
|
|
10084
10245
|
...result && typeof result === "object" ? result : {},
|
|
10085
10246
|
folder_path: input.folder_path,
|
|
10086
10247
|
config_written: input.folder_path ? true : void 0,
|
|
10087
|
-
editor_rules_generated: rulesGenerated.length > 0 ? rulesGenerated : void 0
|
|
10248
|
+
editor_rules_generated: rulesGenerated.length > 0 ? rulesGenerated : void 0,
|
|
10249
|
+
editor_rules_skipped: rulesSkipped.length > 0 ? rulesSkipped : void 0
|
|
10088
10250
|
};
|
|
10089
10251
|
return { content: [{ type: "text", text: formatContent(response) }], structuredContent: toStructured(response) };
|
|
10090
10252
|
}
|
|
@@ -11208,24 +11370,29 @@ Optionally generates AI editor rules for automatic ContextStream usage.`,
|
|
|
11208
11370
|
workspace_id: external_exports.string().uuid().describe("Workspace ID to associate with"),
|
|
11209
11371
|
workspace_name: external_exports.string().optional().describe("Workspace name for reference"),
|
|
11210
11372
|
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")
|
|
11373
|
+
generate_editor_rules: external_exports.boolean().optional().describe("Generate AI editor rules for Windsurf, Cursor, Cline, Kilo Code, Roo Code, Claude Code, and Aider"),
|
|
11374
|
+
overwrite_existing: external_exports.boolean().optional().describe("Allow overwriting existing rule files when generating editor rules")
|
|
11212
11375
|
})
|
|
11213
11376
|
},
|
|
11214
11377
|
async (input) => {
|
|
11215
11378
|
const result = await client.associateWorkspace(input);
|
|
11216
11379
|
let rulesGenerated = [];
|
|
11380
|
+
let rulesSkipped = [];
|
|
11217
11381
|
if (input.generate_editor_rules) {
|
|
11218
11382
|
const ruleResults = await writeEditorRules({
|
|
11219
11383
|
folderPath: input.folder_path,
|
|
11220
11384
|
editors: getAvailableEditors(),
|
|
11221
11385
|
workspaceName: input.workspace_name,
|
|
11222
|
-
workspaceId: input.workspace_id
|
|
11386
|
+
workspaceId: input.workspace_id,
|
|
11387
|
+
overwriteExisting: input.overwrite_existing
|
|
11223
11388
|
});
|
|
11224
11389
|
rulesGenerated = ruleResults.filter((r) => r.status === "created" || r.status === "updated" || r.status === "appended").map((r) => r.status === "created" ? r.filename : `${r.filename} (${r.status})`);
|
|
11390
|
+
rulesSkipped = ruleResults.filter((r) => r.status.startsWith("skipped")).map((r) => r.filename);
|
|
11225
11391
|
}
|
|
11226
11392
|
const response = {
|
|
11227
11393
|
...result,
|
|
11228
|
-
editor_rules_generated: rulesGenerated.length > 0 ? rulesGenerated : void 0
|
|
11394
|
+
editor_rules_generated: rulesGenerated.length > 0 ? rulesGenerated : void 0,
|
|
11395
|
+
editor_rules_skipped: rulesSkipped.length > 0 ? rulesSkipped : void 0
|
|
11229
11396
|
};
|
|
11230
11397
|
return { content: [{ type: "text", text: formatContent(response) }], structuredContent: toStructured(response) };
|
|
11231
11398
|
}
|
|
@@ -11248,6 +11415,7 @@ Behavior:
|
|
|
11248
11415
|
visibility: external_exports.enum(["private", "public"]).optional().describe("Workspace visibility (default: private)"),
|
|
11249
11416
|
create_parent_mapping: external_exports.boolean().optional().describe("Also create a parent folder mapping (e.g., /dev/company/* -> workspace)"),
|
|
11250
11417
|
generate_editor_rules: external_exports.boolean().optional().describe("Generate AI editor rules in the folder for automatic ContextStream usage"),
|
|
11418
|
+
overwrite_existing: external_exports.boolean().optional().describe("Allow overwriting existing rule files when generating editor rules"),
|
|
11251
11419
|
context_hint: external_exports.string().optional().describe("Optional context hint for session initialization"),
|
|
11252
11420
|
auto_index: external_exports.boolean().optional().describe("Automatically create and index project from folder (default: true)")
|
|
11253
11421
|
})
|
|
@@ -11300,14 +11468,17 @@ Behavior:
|
|
|
11300
11468
|
create_parent_mapping: input.create_parent_mapping
|
|
11301
11469
|
});
|
|
11302
11470
|
let rulesGenerated = [];
|
|
11471
|
+
let rulesSkipped = [];
|
|
11303
11472
|
if (input.generate_editor_rules) {
|
|
11304
11473
|
const ruleResults = await writeEditorRules({
|
|
11305
11474
|
folderPath,
|
|
11306
11475
|
editors: getAvailableEditors(),
|
|
11307
11476
|
workspaceName: newWorkspace.name || input.workspace_name,
|
|
11308
|
-
workspaceId: newWorkspace.id
|
|
11477
|
+
workspaceId: newWorkspace.id,
|
|
11478
|
+
overwriteExisting: input.overwrite_existing
|
|
11309
11479
|
});
|
|
11310
11480
|
rulesGenerated = ruleResults.filter((r) => r.status === "created" || r.status === "updated" || r.status === "appended").map((r) => r.status === "created" ? r.filename : `${r.filename} (${r.status})`);
|
|
11481
|
+
rulesSkipped = ruleResults.filter((r) => r.status.startsWith("skipped")).map((r) => r.filename);
|
|
11311
11482
|
}
|
|
11312
11483
|
const session = await client.initSession(
|
|
11313
11484
|
{
|
|
@@ -11332,7 +11503,8 @@ Behavior:
|
|
|
11332
11503
|
name: newWorkspace.name || input.workspace_name
|
|
11333
11504
|
},
|
|
11334
11505
|
association: associateResult,
|
|
11335
|
-
editor_rules_generated: rulesGenerated.length > 0 ? rulesGenerated : void 0
|
|
11506
|
+
editor_rules_generated: rulesGenerated.length > 0 ? rulesGenerated : void 0,
|
|
11507
|
+
editor_rules_skipped: rulesSkipped.length > 0 ? rulesSkipped : void 0
|
|
11336
11508
|
}
|
|
11337
11509
|
};
|
|
11338
11510
|
return { content: [{ type: "text", text: formatContent(response) }], structuredContent: toStructured(response) };
|
|
@@ -11704,6 +11876,98 @@ Example: "What were the auth decisions?" or "What are my TypeScript preferences?
|
|
|
11704
11876
|
return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
|
|
11705
11877
|
}
|
|
11706
11878
|
);
|
|
11879
|
+
registerTool(
|
|
11880
|
+
"generate_rules",
|
|
11881
|
+
{
|
|
11882
|
+
title: "Generate ContextStream rules",
|
|
11883
|
+
description: `Generate AI rule files for editors (Windsurf, Cursor, Cline, Kilo Code, Roo Code, Claude Code, Aider).
|
|
11884
|
+
Defaults to the current project folder; no folder_path required when run from a project.
|
|
11885
|
+
Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
11886
|
+
inputSchema: external_exports.object({
|
|
11887
|
+
folder_path: external_exports.string().optional().describe("Absolute path to the project folder (defaults to IDE root/cwd)"),
|
|
11888
|
+
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."),
|
|
11889
|
+
workspace_name: external_exports.string().optional().describe("Workspace name to include in rules"),
|
|
11890
|
+
workspace_id: external_exports.string().uuid().optional().describe("Workspace ID to include in rules"),
|
|
11891
|
+
project_name: external_exports.string().optional().describe("Project name to include in rules"),
|
|
11892
|
+
additional_rules: external_exports.string().optional().describe("Additional project-specific rules to append"),
|
|
11893
|
+
mode: external_exports.enum(["minimal", "full"]).optional().describe("Rule verbosity mode (default: minimal)"),
|
|
11894
|
+
overwrite_existing: external_exports.boolean().optional().describe("Allow overwriting existing rule files (ContextStream block only)"),
|
|
11895
|
+
apply_global: external_exports.boolean().optional().describe("Also write global rule files for supported editors"),
|
|
11896
|
+
dry_run: external_exports.boolean().optional().describe("If true, return content without writing files")
|
|
11897
|
+
})
|
|
11898
|
+
},
|
|
11899
|
+
async (input) => {
|
|
11900
|
+
const folderPath = resolveFolderPath(input.folder_path, sessionManager);
|
|
11901
|
+
if (!folderPath) {
|
|
11902
|
+
return errorResult("Error: folder_path is required. Provide folder_path or run from a project directory.");
|
|
11903
|
+
}
|
|
11904
|
+
const editors = input.editors?.includes("all") || !input.editors ? getAvailableEditors() : input.editors.filter((e) => e !== "all");
|
|
11905
|
+
const results = [];
|
|
11906
|
+
if (input.dry_run) {
|
|
11907
|
+
for (const editor of editors) {
|
|
11908
|
+
const rule = generateRuleContent(editor, {
|
|
11909
|
+
workspaceName: input.workspace_name,
|
|
11910
|
+
workspaceId: input.workspace_id,
|
|
11911
|
+
projectName: input.project_name,
|
|
11912
|
+
additionalRules: input.additional_rules,
|
|
11913
|
+
mode: input.mode
|
|
11914
|
+
});
|
|
11915
|
+
if (!rule) {
|
|
11916
|
+
results.push({ editor, filename: "", status: "unknown editor" });
|
|
11917
|
+
continue;
|
|
11918
|
+
}
|
|
11919
|
+
results.push({
|
|
11920
|
+
editor,
|
|
11921
|
+
filename: rule.filename,
|
|
11922
|
+
status: "dry run - would update"
|
|
11923
|
+
});
|
|
11924
|
+
}
|
|
11925
|
+
} else {
|
|
11926
|
+
const writeResults = await writeEditorRules({
|
|
11927
|
+
folderPath,
|
|
11928
|
+
editors,
|
|
11929
|
+
workspaceName: input.workspace_name,
|
|
11930
|
+
workspaceId: input.workspace_id,
|
|
11931
|
+
projectName: input.project_name,
|
|
11932
|
+
additionalRules: input.additional_rules,
|
|
11933
|
+
mode: input.mode,
|
|
11934
|
+
overwriteExisting: input.overwrite_existing
|
|
11935
|
+
});
|
|
11936
|
+
results.push(...writeResults);
|
|
11937
|
+
}
|
|
11938
|
+
const globalTargets = listGlobalRuleTargets(editors);
|
|
11939
|
+
let globalResults;
|
|
11940
|
+
if (input.apply_global) {
|
|
11941
|
+
if (input.dry_run) {
|
|
11942
|
+
globalResults = globalTargets.map((target) => ({
|
|
11943
|
+
editor: target.editor,
|
|
11944
|
+
filename: target.filePath,
|
|
11945
|
+
status: "dry run - would update",
|
|
11946
|
+
scope: "global"
|
|
11947
|
+
}));
|
|
11948
|
+
} else {
|
|
11949
|
+
globalResults = await writeGlobalRules({
|
|
11950
|
+
editors,
|
|
11951
|
+
mode: input.mode,
|
|
11952
|
+
overwriteExisting: input.overwrite_existing
|
|
11953
|
+
});
|
|
11954
|
+
}
|
|
11955
|
+
}
|
|
11956
|
+
const createdCount = results.filter((r) => r.status === "created" || r.status === "updated" || r.status === "appended").length;
|
|
11957
|
+
const skippedCount = results.filter((r) => r.status.startsWith("skipped")).length;
|
|
11958
|
+
const baseMessage = 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.`;
|
|
11959
|
+
const globalPrompt = input.apply_global ? "Global rule update complete." : globalTargets.length > 0 ? "Apply rules globally too? Re-run with apply_global: true." : "No global rule locations are known for these editors.";
|
|
11960
|
+
const summary = {
|
|
11961
|
+
folder: folderPath,
|
|
11962
|
+
results,
|
|
11963
|
+
...globalResults ? { global_results: globalResults } : {},
|
|
11964
|
+
...globalTargets.length > 0 ? { global_targets: globalTargets } : {},
|
|
11965
|
+
message: baseMessage,
|
|
11966
|
+
global_prompt: globalPrompt
|
|
11967
|
+
};
|
|
11968
|
+
return { content: [{ type: "text", text: formatContent(summary) }], structuredContent: toStructured(summary) };
|
|
11969
|
+
}
|
|
11970
|
+
);
|
|
11707
11971
|
registerTool(
|
|
11708
11972
|
"generate_editor_rules",
|
|
11709
11973
|
{
|
|
@@ -11719,6 +11983,7 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
11719
11983
|
project_name: external_exports.string().optional().describe("Project name to include in rules"),
|
|
11720
11984
|
additional_rules: external_exports.string().optional().describe("Additional project-specific rules to append"),
|
|
11721
11985
|
mode: external_exports.enum(["minimal", "full"]).optional().describe("Rule verbosity mode (default: minimal)"),
|
|
11986
|
+
overwrite_existing: external_exports.boolean().optional().describe("Allow overwriting existing rule files (ContextStream block only)"),
|
|
11722
11987
|
dry_run: external_exports.boolean().optional().describe("If true, return content without writing files")
|
|
11723
11988
|
})
|
|
11724
11989
|
},
|
|
@@ -11757,14 +12022,17 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
11757
12022
|
workspaceId: input.workspace_id,
|
|
11758
12023
|
projectName: input.project_name,
|
|
11759
12024
|
additionalRules: input.additional_rules,
|
|
11760
|
-
mode: input.mode
|
|
12025
|
+
mode: input.mode,
|
|
12026
|
+
overwriteExisting: input.overwrite_existing
|
|
11761
12027
|
});
|
|
11762
12028
|
results.push(...writeResults);
|
|
11763
12029
|
}
|
|
12030
|
+
const createdCount = results.filter((r) => r.status === "created" || r.status === "updated" || r.status === "appended").length;
|
|
12031
|
+
const skippedCount = results.filter((r) => r.status.startsWith("skipped")).length;
|
|
11764
12032
|
const summary = {
|
|
11765
12033
|
folder: folderPath,
|
|
11766
12034
|
results,
|
|
11767
|
-
message: input.dry_run ? "Dry run complete. Use dry_run: false to write files." : `Generated ${
|
|
12035
|
+
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
12036
|
};
|
|
11769
12037
|
return { content: [{ type: "text", text: formatContent(summary) }], structuredContent: toStructured(summary) };
|
|
11770
12038
|
}
|
|
@@ -15050,7 +15318,8 @@ function registerPrompts(server) {
|
|
|
15050
15318
|
"- Otherwise ask me for an absolute folder path.",
|
|
15051
15319
|
"- Ask which editor(s) (windsurf,cursor,cline,kilo,roo,claude,aider) or default to all.",
|
|
15052
15320
|
"",
|
|
15053
|
-
"Then call `
|
|
15321
|
+
"Then call `generate_rules` and confirm which files were created/updated.",
|
|
15322
|
+
"Ask if the user also wants to apply rules globally (pass apply_global: true)."
|
|
15054
15323
|
].join("\n")
|
|
15055
15324
|
}
|
|
15056
15325
|
}
|
|
@@ -15821,6 +16090,19 @@ var LEGACY_CONTEXTSTREAM_ALLOWED_HEADINGS2 = [
|
|
|
15821
16090
|
"plans & tasks",
|
|
15822
16091
|
"complete action reference"
|
|
15823
16092
|
];
|
|
16093
|
+
var CONTEXTSTREAM_PREAMBLE_PATTERNS2 = [
|
|
16094
|
+
/^#\s+workspace:/i,
|
|
16095
|
+
/^#\s+project:/i,
|
|
16096
|
+
/^#\s+workspace id:/i,
|
|
16097
|
+
/^#\s+codex cli instructions$/i,
|
|
16098
|
+
/^#\s+claude code instructions$/i,
|
|
16099
|
+
/^#\s+cursor rules$/i,
|
|
16100
|
+
/^#\s+windsurf rules$/i,
|
|
16101
|
+
/^#\s+cline rules$/i,
|
|
16102
|
+
/^#\s+kilo code rules$/i,
|
|
16103
|
+
/^#\s+roo code rules$/i,
|
|
16104
|
+
/^#\s+aider configuration$/i
|
|
16105
|
+
];
|
|
15824
16106
|
function wrapWithMarkers2(content) {
|
|
15825
16107
|
return `${CONTEXTSTREAM_START_MARKER2}
|
|
15826
16108
|
${content.trim()}
|
|
@@ -15841,6 +16123,96 @@ function isLegacyContextStreamRules2(content) {
|
|
|
15841
16123
|
}
|
|
15842
16124
|
return hasHeading;
|
|
15843
16125
|
}
|
|
16126
|
+
function isContextStreamPreamble2(line) {
|
|
16127
|
+
const trimmed = line.trim();
|
|
16128
|
+
if (!trimmed) return false;
|
|
16129
|
+
return CONTEXTSTREAM_PREAMBLE_PATTERNS2.some((pattern) => pattern.test(trimmed));
|
|
16130
|
+
}
|
|
16131
|
+
function findContextStreamHeading2(lines) {
|
|
16132
|
+
const headingRegex = /^(#{1,6})\s+(.+)$/;
|
|
16133
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
16134
|
+
const match = headingRegex.exec(lines[i]);
|
|
16135
|
+
if (!match) continue;
|
|
16136
|
+
if (match[2].toLowerCase().includes("contextstream")) {
|
|
16137
|
+
return { index: i, level: match[1].length };
|
|
16138
|
+
}
|
|
16139
|
+
}
|
|
16140
|
+
return null;
|
|
16141
|
+
}
|
|
16142
|
+
function findSectionEnd2(lines, startLine, level) {
|
|
16143
|
+
const headingRegex = /^(#{1,6})\s+(.+)$/;
|
|
16144
|
+
for (let i = startLine + 1; i < lines.length; i += 1) {
|
|
16145
|
+
const match = headingRegex.exec(lines[i]);
|
|
16146
|
+
if (!match) continue;
|
|
16147
|
+
if (match[1].length <= level) return i;
|
|
16148
|
+
}
|
|
16149
|
+
return lines.length;
|
|
16150
|
+
}
|
|
16151
|
+
function extractContextStreamBlock2(content) {
|
|
16152
|
+
const lines = content.split(/\r?\n/);
|
|
16153
|
+
const heading = findContextStreamHeading2(lines);
|
|
16154
|
+
if (!heading) return content.trim();
|
|
16155
|
+
const endLine = findSectionEnd2(lines, heading.index, heading.level);
|
|
16156
|
+
return lines.slice(heading.index, endLine).join("\n").trim();
|
|
16157
|
+
}
|
|
16158
|
+
function findLegacyContextStreamSection2(content) {
|
|
16159
|
+
const lines = content.split(/\r?\n/);
|
|
16160
|
+
const heading = findContextStreamHeading2(lines);
|
|
16161
|
+
if (!heading) return null;
|
|
16162
|
+
let startLine = heading.index;
|
|
16163
|
+
for (let i = heading.index - 1; i >= 0; i -= 1) {
|
|
16164
|
+
const line = lines[i];
|
|
16165
|
+
if (!line.trim()) {
|
|
16166
|
+
startLine = i;
|
|
16167
|
+
continue;
|
|
16168
|
+
}
|
|
16169
|
+
if (isContextStreamPreamble2(line)) {
|
|
16170
|
+
startLine = i;
|
|
16171
|
+
continue;
|
|
16172
|
+
}
|
|
16173
|
+
break;
|
|
16174
|
+
}
|
|
16175
|
+
const endLine = findSectionEnd2(lines, heading.index, heading.level);
|
|
16176
|
+
return { startLine, endLine, contextLine: heading.index };
|
|
16177
|
+
}
|
|
16178
|
+
function blockHasPreamble2(block) {
|
|
16179
|
+
const lines = block.split(/\r?\n/);
|
|
16180
|
+
for (const line of lines) {
|
|
16181
|
+
const trimmed = line.trim();
|
|
16182
|
+
if (!trimmed) continue;
|
|
16183
|
+
if (isContextStreamPreamble2(trimmed)) return true;
|
|
16184
|
+
if (/^#{1,6}\s+/.test(trimmed)) return false;
|
|
16185
|
+
}
|
|
16186
|
+
return false;
|
|
16187
|
+
}
|
|
16188
|
+
function replaceContextStreamBlock2(existing, content) {
|
|
16189
|
+
const fullWrapped = wrapWithMarkers2(content);
|
|
16190
|
+
const blockWrapped = wrapWithMarkers2(extractContextStreamBlock2(content));
|
|
16191
|
+
const startIdx = existing.indexOf(CONTEXTSTREAM_START_MARKER2);
|
|
16192
|
+
const endIdx = existing.indexOf(CONTEXTSTREAM_END_MARKER2);
|
|
16193
|
+
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
16194
|
+
const existingBlock = existing.slice(startIdx + CONTEXTSTREAM_START_MARKER2.length, endIdx);
|
|
16195
|
+
const replacement = blockHasPreamble2(existingBlock) ? fullWrapped : blockWrapped;
|
|
16196
|
+
const before = existing.substring(0, startIdx).trimEnd();
|
|
16197
|
+
const after = existing.substring(endIdx + CONTEXTSTREAM_END_MARKER2.length).trimStart();
|
|
16198
|
+
const merged = [before, replacement, after].filter((part) => part.length > 0).join("\n\n");
|
|
16199
|
+
return { content: merged.trim() + "\n", status: "updated" };
|
|
16200
|
+
}
|
|
16201
|
+
const legacy = findLegacyContextStreamSection2(existing);
|
|
16202
|
+
if (legacy) {
|
|
16203
|
+
const lines = existing.split(/\r?\n/);
|
|
16204
|
+
const before = lines.slice(0, legacy.startLine).join("\n").trimEnd();
|
|
16205
|
+
const after = lines.slice(legacy.endLine).join("\n").trimStart();
|
|
16206
|
+
const replacement = legacy.startLine < legacy.contextLine ? fullWrapped : blockWrapped;
|
|
16207
|
+
const merged = [before, replacement, after].filter((part) => part.length > 0).join("\n\n");
|
|
16208
|
+
return { content: merged.trim() + "\n", status: "updated" };
|
|
16209
|
+
}
|
|
16210
|
+
if (isLegacyContextStreamRules2(existing)) {
|
|
16211
|
+
return { content: fullWrapped + "\n", status: "updated" };
|
|
16212
|
+
}
|
|
16213
|
+
const appended = existing.trimEnd() + "\n\n" + blockWrapped + "\n";
|
|
16214
|
+
return { content: appended, status: "appended" };
|
|
16215
|
+
}
|
|
15844
16216
|
async function upsertTextFile(filePath, content, _marker) {
|
|
15845
16217
|
await fs5.mkdir(path6.dirname(filePath), { recursive: true });
|
|
15846
16218
|
const exists = await fileExists(filePath);
|
|
@@ -15850,22 +16222,13 @@ async function upsertTextFile(filePath, content, _marker) {
|
|
|
15850
16222
|
return "created";
|
|
15851
16223
|
}
|
|
15852
16224
|
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)) {
|
|
16225
|
+
if (!existing.trim()) {
|
|
15863
16226
|
await fs5.writeFile(filePath, wrappedContent + "\n", "utf8");
|
|
15864
16227
|
return "updated";
|
|
15865
16228
|
}
|
|
15866
|
-
const
|
|
15867
|
-
await fs5.writeFile(filePath,
|
|
15868
|
-
return
|
|
16229
|
+
const replaced = replaceContextStreamBlock2(existing, content);
|
|
16230
|
+
await fs5.writeFile(filePath, replaced.content, "utf8");
|
|
16231
|
+
return replaced.status;
|
|
15869
16232
|
}
|
|
15870
16233
|
function globalRulesPathForEditor(editor) {
|
|
15871
16234
|
const home = homedir4();
|
|
@@ -16250,6 +16613,27 @@ async function runSetupWizard(args) {
|
|
|
16250
16613
|
const dryRun = args.includes("--dry-run");
|
|
16251
16614
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
16252
16615
|
const writeActions = [];
|
|
16616
|
+
let overwriteAllRules = null;
|
|
16617
|
+
let skipAllRules = false;
|
|
16618
|
+
const confirmOverwriteRules = async (filePath) => {
|
|
16619
|
+
if (dryRun) return true;
|
|
16620
|
+
if (skipAllRules) return false;
|
|
16621
|
+
if (overwriteAllRules) return true;
|
|
16622
|
+
const exists = await fileExists(filePath);
|
|
16623
|
+
if (!exists) return true;
|
|
16624
|
+
const answer = normalizeInput(
|
|
16625
|
+
await rl.question(`Rules file already exists at ${filePath}. Replace ContextStream block? [y/N/a/s]: `)
|
|
16626
|
+
).toLowerCase();
|
|
16627
|
+
if (answer === "a" || answer === "all") {
|
|
16628
|
+
overwriteAllRules = true;
|
|
16629
|
+
return true;
|
|
16630
|
+
}
|
|
16631
|
+
if (answer === "s" || answer === "skip-all" || answer === "none") {
|
|
16632
|
+
skipAllRules = true;
|
|
16633
|
+
return false;
|
|
16634
|
+
}
|
|
16635
|
+
return answer === "y" || answer === "yes";
|
|
16636
|
+
};
|
|
16253
16637
|
try {
|
|
16254
16638
|
console.log(`ContextStream Setup Wizard (v${VERSION})`);
|
|
16255
16639
|
console.log("This configures ContextStream MCP + rules for your AI editor(s).");
|
|
@@ -16622,6 +17006,12 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
16622
17006
|
console.log(`- ${EDITOR_LABELS[editor]}: would write ${filePath}`);
|
|
16623
17007
|
continue;
|
|
16624
17008
|
}
|
|
17009
|
+
const allowOverwrite = await confirmOverwriteRules(filePath);
|
|
17010
|
+
if (!allowOverwrite) {
|
|
17011
|
+
writeActions.push({ kind: "rules", target: filePath, status: "skipped" });
|
|
17012
|
+
console.log(`- ${EDITOR_LABELS[editor]}: skipped ${filePath}`);
|
|
17013
|
+
continue;
|
|
17014
|
+
}
|
|
16625
17015
|
const status = await upsertTextFile(filePath, rule.content, "ContextStream");
|
|
16626
17016
|
writeActions.push({ kind: "rules", target: filePath, status });
|
|
16627
17017
|
console.log(`- ${EDITOR_LABELS[editor]}: ${status} ${filePath}`);
|
|
@@ -16757,6 +17147,11 @@ Applying to ${projects.length} project(s)...`);
|
|
|
16757
17147
|
continue;
|
|
16758
17148
|
}
|
|
16759
17149
|
try {
|
|
17150
|
+
const allowOverwrite = await confirmOverwriteRules(filePath);
|
|
17151
|
+
if (!allowOverwrite) {
|
|
17152
|
+
writeActions.push({ kind: "rules", target: filePath, status: "skipped" });
|
|
17153
|
+
continue;
|
|
17154
|
+
}
|
|
16760
17155
|
const status = await upsertTextFile(filePath, rule.content, "ContextStream");
|
|
16761
17156
|
writeActions.push({ kind: "rules", target: filePath, status });
|
|
16762
17157
|
} 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.21",
|
|
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",
|