@contextstream/mcp-server 0.4.81 → 0.4.82
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/README.md +40 -15
- package/dist/hooks/auto-rules.js +162 -17
- package/dist/hooks/post-write.js +703 -28
- package/dist/hooks/runner.js +871 -147
- package/dist/hooks/session-init.js +37 -3
- package/dist/hooks/user-prompt-submit.js +11 -4
- package/dist/index.js +1038 -428
- package/package.json +6 -5
package/dist/index.js
CHANGED
|
@@ -11,11 +11,20 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
11
11
|
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
12
12
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
13
13
|
});
|
|
14
|
-
var __esm = (fn, res) => function __init() {
|
|
15
|
-
|
|
14
|
+
var __esm = (fn, res, err) => function __init() {
|
|
15
|
+
if (err) throw err[0];
|
|
16
|
+
try {
|
|
17
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
18
|
+
} catch (e) {
|
|
19
|
+
throw err = [e], e;
|
|
20
|
+
}
|
|
16
21
|
};
|
|
17
22
|
var __commonJS = (cb, mod) => function __require2() {
|
|
18
|
-
|
|
23
|
+
try {
|
|
24
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
25
|
+
} catch (e) {
|
|
26
|
+
throw mod = 0, e;
|
|
27
|
+
}
|
|
19
28
|
};
|
|
20
29
|
var __export = (target, all) => {
|
|
21
30
|
for (var name in all)
|
|
@@ -4441,7 +4450,7 @@ function detectUpdateMethod() {
|
|
|
4441
4450
|
return "curl";
|
|
4442
4451
|
}
|
|
4443
4452
|
async function runUpdate(method) {
|
|
4444
|
-
return new Promise((
|
|
4453
|
+
return new Promise((resolve17, reject) => {
|
|
4445
4454
|
let command;
|
|
4446
4455
|
let args;
|
|
4447
4456
|
let shell;
|
|
@@ -4472,13 +4481,13 @@ async function runUpdate(method) {
|
|
|
4472
4481
|
});
|
|
4473
4482
|
proc.on("close", (code) => {
|
|
4474
4483
|
if (code === 0) {
|
|
4475
|
-
|
|
4484
|
+
resolve17();
|
|
4476
4485
|
} else {
|
|
4477
4486
|
reject(new Error(`Update process exited with code ${code}`));
|
|
4478
4487
|
}
|
|
4479
4488
|
});
|
|
4480
4489
|
proc.unref();
|
|
4481
|
-
setTimeout(() =>
|
|
4490
|
+
setTimeout(() => resolve17(), 1e3);
|
|
4482
4491
|
});
|
|
4483
4492
|
}
|
|
4484
4493
|
function writeUpdateMarker(previousVersion, newVersion) {
|
|
@@ -4665,7 +4674,7 @@ function setActiveModelId(modelId) {
|
|
|
4665
4674
|
activeModelId = trimmed ? trimmed : null;
|
|
4666
4675
|
}
|
|
4667
4676
|
async function sleep(ms) {
|
|
4668
|
-
return new Promise((
|
|
4677
|
+
return new Promise((resolve17) => setTimeout(resolve17, ms));
|
|
4669
4678
|
}
|
|
4670
4679
|
async function request(config, path23, options = {}) {
|
|
4671
4680
|
const { apiUrl, userAgent } = config;
|
|
@@ -5330,39 +5339,119 @@ var require_ignore = __commonJS({
|
|
|
5330
5339
|
// src/ignore.ts
|
|
5331
5340
|
import * as fs from "fs";
|
|
5332
5341
|
import * as path from "path";
|
|
5342
|
+
function isInvariantIgnoredPath(pathname) {
|
|
5343
|
+
const normalized = pathname.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
5344
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
5345
|
+
if (segments.some((segment) => INVARIANT_IGNORED_DIRECTORIES.has(segment.toLowerCase()))) {
|
|
5346
|
+
return true;
|
|
5347
|
+
}
|
|
5348
|
+
const basename5 = (segments.at(-1) || "").toLowerCase();
|
|
5349
|
+
if (INVARIANT_SECRET_FILENAMES.has(basename5)) return true;
|
|
5350
|
+
if (INVARIANT_SECRET_SUFFIXES.some((suffix) => basename5.endsWith(suffix))) return true;
|
|
5351
|
+
return (basename5 === ".env" || basename5.startsWith(".env.")) && !ENV_TEMPLATE_ALLOWLIST.has(basename5);
|
|
5352
|
+
}
|
|
5333
5353
|
async function loadIgnorePatterns(projectRoot) {
|
|
5334
5354
|
const ig = (0, import_ignore.default)();
|
|
5335
5355
|
const patterns = [...DEFAULT_IGNORE_PATTERNS];
|
|
5336
5356
|
ig.add(DEFAULT_IGNORE_PATTERNS);
|
|
5337
|
-
const ignoreFilePath = path.join(projectRoot, IGNORE_FILENAME);
|
|
5338
5357
|
let hasUserPatterns = false;
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5358
|
+
for (const filename of IGNORE_FILENAMES) {
|
|
5359
|
+
try {
|
|
5360
|
+
const content = await fs.promises.readFile(path.join(projectRoot, filename), "utf-8");
|
|
5361
|
+
const userPatterns = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
5362
|
+
if (userPatterns.length > 0) {
|
|
5363
|
+
ig.add(userPatterns);
|
|
5364
|
+
patterns.push(...userPatterns);
|
|
5365
|
+
hasUserPatterns = true;
|
|
5366
|
+
}
|
|
5367
|
+
} catch {
|
|
5368
|
+
}
|
|
5369
|
+
}
|
|
5370
|
+
return {
|
|
5371
|
+
ignores: (pathname) => isInvariantIgnoredPath(pathname) || ig.ignores(pathname),
|
|
5372
|
+
patterns,
|
|
5373
|
+
hasUserPatterns
|
|
5374
|
+
};
|
|
5375
|
+
}
|
|
5376
|
+
function loadIgnorePatternsSync(projectRoot) {
|
|
5377
|
+
const ig = (0, import_ignore.default)();
|
|
5378
|
+
const patterns = [...DEFAULT_IGNORE_PATTERNS];
|
|
5379
|
+
ig.add(DEFAULT_IGNORE_PATTERNS);
|
|
5380
|
+
let hasUserPatterns = false;
|
|
5381
|
+
for (const filename of IGNORE_FILENAMES) {
|
|
5382
|
+
try {
|
|
5383
|
+
const content = fs.readFileSync(path.join(projectRoot, filename), "utf-8");
|
|
5384
|
+
const userPatterns = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
5385
|
+
if (userPatterns.length > 0) {
|
|
5386
|
+
ig.add(userPatterns);
|
|
5387
|
+
patterns.push(...userPatterns);
|
|
5388
|
+
hasUserPatterns = true;
|
|
5389
|
+
}
|
|
5390
|
+
} catch {
|
|
5346
5391
|
}
|
|
5347
|
-
} catch {
|
|
5348
5392
|
}
|
|
5349
5393
|
return {
|
|
5350
|
-
ignores: (pathname) => ig.ignores(pathname),
|
|
5394
|
+
ignores: (pathname) => isInvariantIgnoredPath(pathname) || ig.ignores(pathname),
|
|
5351
5395
|
patterns,
|
|
5352
5396
|
hasUserPatterns
|
|
5353
5397
|
};
|
|
5354
5398
|
}
|
|
5355
|
-
var import_ignore,
|
|
5399
|
+
var import_ignore, IGNORE_FILENAMES, INVARIANT_IGNORED_DIRECTORIES, INVARIANT_SECRET_FILENAMES, INVARIANT_SECRET_SUFFIXES, ENV_TEMPLATE_ALLOWLIST, DEFAULT_IGNORE_PATTERNS;
|
|
5356
5400
|
var init_ignore = __esm({
|
|
5357
5401
|
"src/ignore.ts"() {
|
|
5358
5402
|
"use strict";
|
|
5359
5403
|
import_ignore = __toESM(require_ignore(), 1);
|
|
5360
|
-
|
|
5404
|
+
IGNORE_FILENAMES = [".gitignore", ".contextignore", ".contextstream/ignore"];
|
|
5405
|
+
INVARIANT_IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
5406
|
+
".contextstream",
|
|
5407
|
+
".claude",
|
|
5408
|
+
".codex",
|
|
5409
|
+
".cursor",
|
|
5410
|
+
".windsurf",
|
|
5411
|
+
".roo",
|
|
5412
|
+
".kilocode",
|
|
5413
|
+
".ssh",
|
|
5414
|
+
".aws",
|
|
5415
|
+
".gnupg",
|
|
5416
|
+
".kube",
|
|
5417
|
+
".docker"
|
|
5418
|
+
]);
|
|
5419
|
+
INVARIANT_SECRET_FILENAMES = /* @__PURE__ */ new Set([
|
|
5420
|
+
".npmrc",
|
|
5421
|
+
".pypirc",
|
|
5422
|
+
".netrc",
|
|
5423
|
+
".pgpass",
|
|
5424
|
+
".git-credentials",
|
|
5425
|
+
"id_rsa",
|
|
5426
|
+
"id_ed25519",
|
|
5427
|
+
"id_ecdsa",
|
|
5428
|
+
"known_hosts",
|
|
5429
|
+
"credentials.json",
|
|
5430
|
+
"serviceaccountkey.json",
|
|
5431
|
+
"auth.json"
|
|
5432
|
+
]);
|
|
5433
|
+
INVARIANT_SECRET_SUFFIXES = [".pem", ".key", ".p12", ".pfx", ".jks"];
|
|
5434
|
+
ENV_TEMPLATE_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
5435
|
+
".env.example",
|
|
5436
|
+
".env.sample",
|
|
5437
|
+
".env.template",
|
|
5438
|
+
".env.dist",
|
|
5439
|
+
".env.defaults"
|
|
5440
|
+
]);
|
|
5361
5441
|
DEFAULT_IGNORE_PATTERNS = [
|
|
5362
5442
|
// Version control
|
|
5363
5443
|
".git/",
|
|
5364
5444
|
".svn/",
|
|
5365
5445
|
".hg/",
|
|
5446
|
+
// ContextStream and AI editor/agent state. These directories can contain
|
|
5447
|
+
// credentials, generated rules, caches, and nested worktrees.
|
|
5448
|
+
".contextstream/",
|
|
5449
|
+
".claude/",
|
|
5450
|
+
".codex/",
|
|
5451
|
+
".cursor/",
|
|
5452
|
+
".windsurf/",
|
|
5453
|
+
".roo/",
|
|
5454
|
+
".kilocode/",
|
|
5366
5455
|
// Package managers / dependencies
|
|
5367
5456
|
"node_modules/",
|
|
5368
5457
|
"vendor/",
|
|
@@ -5393,6 +5482,12 @@ var init_ignore = __esm({
|
|
|
5393
5482
|
".idea/",
|
|
5394
5483
|
".vscode/",
|
|
5395
5484
|
".vs/",
|
|
5485
|
+
// Credential stores
|
|
5486
|
+
".ssh/",
|
|
5487
|
+
".aws/",
|
|
5488
|
+
".gnupg/",
|
|
5489
|
+
".kube/",
|
|
5490
|
+
".docker/",
|
|
5396
5491
|
// Coverage
|
|
5397
5492
|
"coverage/",
|
|
5398
5493
|
".coverage/",
|
|
@@ -5406,6 +5501,22 @@ var init_ignore = __esm({
|
|
|
5406
5501
|
"composer.lock",
|
|
5407
5502
|
"*.min.js",
|
|
5408
5503
|
"*.min.css",
|
|
5504
|
+
"*.pem",
|
|
5505
|
+
"*.key",
|
|
5506
|
+
"*.p12",
|
|
5507
|
+
"*.pfx",
|
|
5508
|
+
"*.jks",
|
|
5509
|
+
".npmrc",
|
|
5510
|
+
".pypirc",
|
|
5511
|
+
".netrc",
|
|
5512
|
+
".pgpass",
|
|
5513
|
+
".git-credentials",
|
|
5514
|
+
"id_rsa",
|
|
5515
|
+
"id_ed25519",
|
|
5516
|
+
"id_ecdsa",
|
|
5517
|
+
"credentials.json",
|
|
5518
|
+
"serviceAccountKey.json",
|
|
5519
|
+
"auth.json",
|
|
5409
5520
|
// OS files
|
|
5410
5521
|
".DS_Store",
|
|
5411
5522
|
"Thumbs.db"
|
|
@@ -5433,6 +5544,15 @@ import { exec } from "child_process";
|
|
|
5433
5544
|
import { promisify } from "util";
|
|
5434
5545
|
import * as os from "os";
|
|
5435
5546
|
import * as crypto from "crypto";
|
|
5547
|
+
function shouldSkipSensitiveFile(name) {
|
|
5548
|
+
const lower = name.toLowerCase();
|
|
5549
|
+
if (SECRET_FILENAMES.has(lower)) return true;
|
|
5550
|
+
if (SECRET_FILE_SUFFIXES.some((suffix) => lower.endsWith(suffix))) return true;
|
|
5551
|
+
if ((lower === ".env" || lower.startsWith(".env.")) && !ENV_TEMPLATE_ALLOWLIST2.has(lower)) {
|
|
5552
|
+
return true;
|
|
5553
|
+
}
|
|
5554
|
+
return false;
|
|
5555
|
+
}
|
|
5436
5556
|
function getMachineId() {
|
|
5437
5557
|
const hostname2 = os.hostname();
|
|
5438
5558
|
const hash = crypto.createHash("sha256").update(hostname2).digest("hex");
|
|
@@ -5539,6 +5659,7 @@ function clearGitContextCache() {
|
|
|
5539
5659
|
gitContextCache.clear();
|
|
5540
5660
|
}
|
|
5541
5661
|
async function readFilesFromDirectory(rootPath, options = {}) {
|
|
5662
|
+
if (isInvariantIgnoredPath(path2.resolve(rootPath))) return [];
|
|
5542
5663
|
const maxFiles = options.maxFiles ?? MAX_FILES_PER_BATCH;
|
|
5543
5664
|
const maxFileSize = options.maxFileSize ?? MAX_FILE_SIZE;
|
|
5544
5665
|
const files = [];
|
|
@@ -5563,6 +5684,7 @@ async function readFilesFromDirectory(rootPath, options = {}) {
|
|
|
5563
5684
|
await walkDir(fullPath, relPath);
|
|
5564
5685
|
} else if (entry.isFile()) {
|
|
5565
5686
|
if (IGNORE_FILES.has(entry.name)) continue;
|
|
5687
|
+
if (shouldSkipSensitiveFile(entry.name)) continue;
|
|
5566
5688
|
if (ig.ignores(relPath)) continue;
|
|
5567
5689
|
const ext = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
5568
5690
|
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
@@ -5598,6 +5720,7 @@ async function readFilesFromDirectory(rootPath, options = {}) {
|
|
|
5598
5720
|
return files;
|
|
5599
5721
|
}
|
|
5600
5722
|
async function* readAllFilesInBatches(rootPath, options = {}) {
|
|
5723
|
+
if (isInvariantIgnoredPath(path2.resolve(rootPath))) return;
|
|
5601
5724
|
const maxBatchBytes = options.maxBatchBytes ?? MAX_BATCH_BYTES;
|
|
5602
5725
|
const largeFileThreshold = options.largeFileThreshold ?? LARGE_FILE_THRESHOLD;
|
|
5603
5726
|
const maxFilesPerBatch = options.maxFilesPerBatch ?? options.batchSize ?? MAX_FILES_PER_BATCH;
|
|
@@ -5623,6 +5746,7 @@ async function* readAllFilesInBatches(rootPath, options = {}) {
|
|
|
5623
5746
|
yield* walkDir(fullPath, relPath);
|
|
5624
5747
|
} else if (entry.isFile()) {
|
|
5625
5748
|
if (IGNORE_FILES.has(entry.name)) continue;
|
|
5749
|
+
if (shouldSkipSensitiveFile(entry.name)) continue;
|
|
5626
5750
|
if (ig.ignores(relPath)) continue;
|
|
5627
5751
|
const ext = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
5628
5752
|
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
@@ -5683,6 +5807,7 @@ async function* readAllFilesInBatches(rootPath, options = {}) {
|
|
|
5683
5807
|
}
|
|
5684
5808
|
}
|
|
5685
5809
|
async function* readChangedFilesInBatches(rootPath, sinceTimestamp, options = {}) {
|
|
5810
|
+
if (isInvariantIgnoredPath(path2.resolve(rootPath))) return;
|
|
5686
5811
|
const maxBatchBytes = options.maxBatchBytes ?? MAX_BATCH_BYTES;
|
|
5687
5812
|
const largeFileThreshold = options.largeFileThreshold ?? LARGE_FILE_THRESHOLD;
|
|
5688
5813
|
const maxFilesPerBatch = options.maxFilesPerBatch ?? options.batchSize ?? MAX_FILES_PER_BATCH;
|
|
@@ -5711,6 +5836,7 @@ async function* readChangedFilesInBatches(rootPath, sinceTimestamp, options = {}
|
|
|
5711
5836
|
yield* walkDir(fullPath, relPath);
|
|
5712
5837
|
} else if (entry.isFile()) {
|
|
5713
5838
|
if (IGNORE_FILES.has(entry.name)) continue;
|
|
5839
|
+
if (shouldSkipSensitiveFile(entry.name)) continue;
|
|
5714
5840
|
if (ig.ignores(relPath)) continue;
|
|
5715
5841
|
const ext = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
5716
5842
|
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
@@ -5777,6 +5903,7 @@ async function* readChangedFilesInBatches(rootPath, sinceTimestamp, options = {}
|
|
|
5777
5903
|
);
|
|
5778
5904
|
}
|
|
5779
5905
|
async function countIndexableFiles(rootPath, options = {}) {
|
|
5906
|
+
if (isInvariantIgnoredPath(path2.resolve(rootPath))) return { count: 0, stopped: false };
|
|
5780
5907
|
const maxFiles = options.maxFiles ?? 1;
|
|
5781
5908
|
const maxFileSize = options.maxFileSize ?? MAX_FILE_SIZE;
|
|
5782
5909
|
const ig = options.ignoreInstance ?? await loadIgnorePatterns(rootPath);
|
|
@@ -5807,6 +5934,7 @@ async function countIndexableFiles(rootPath, options = {}) {
|
|
|
5807
5934
|
await walkDir(fullPath, relPath);
|
|
5808
5935
|
} else if (entry.isFile()) {
|
|
5809
5936
|
if (IGNORE_FILES.has(entry.name)) continue;
|
|
5937
|
+
if (shouldSkipSensitiveFile(entry.name)) continue;
|
|
5810
5938
|
if (ig.ignores(relPath)) continue;
|
|
5811
5939
|
const ext = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
5812
5940
|
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
@@ -5891,7 +6019,7 @@ function deleteHashManifest(projectId) {
|
|
|
5891
6019
|
} catch {
|
|
5892
6020
|
}
|
|
5893
6021
|
}
|
|
5894
|
-
var execAsync, CODE_EXTENSIONS, IGNORE_DIRS, IGNORE_FILES, MAX_FILE_SIZE, MAX_BATCH_BYTES, LARGE_FILE_THRESHOLD, MAX_FILES_PER_BATCH, gitContextCache;
|
|
6022
|
+
var execAsync, CODE_EXTENSIONS, IGNORE_DIRS, IGNORE_FILES, SECRET_FILENAMES, SECRET_FILE_SUFFIXES, ENV_TEMPLATE_ALLOWLIST2, MAX_FILE_SIZE, MAX_BATCH_BYTES, LARGE_FILE_THRESHOLD, MAX_FILES_PER_BATCH, gitContextCache;
|
|
5895
6023
|
var init_files = __esm({
|
|
5896
6024
|
"src/files.ts"() {
|
|
5897
6025
|
"use strict";
|
|
@@ -5967,6 +6095,13 @@ var init_files = __esm({
|
|
|
5967
6095
|
IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
5968
6096
|
"node_modules",
|
|
5969
6097
|
".git",
|
|
6098
|
+
".contextstream",
|
|
6099
|
+
".claude",
|
|
6100
|
+
".codex",
|
|
6101
|
+
".cursor",
|
|
6102
|
+
".windsurf",
|
|
6103
|
+
".roo",
|
|
6104
|
+
".kilocode",
|
|
5970
6105
|
".svn",
|
|
5971
6106
|
".hg",
|
|
5972
6107
|
"target",
|
|
@@ -5994,7 +6129,18 @@ var init_files = __esm({
|
|
|
5994
6129
|
"obj",
|
|
5995
6130
|
".idea",
|
|
5996
6131
|
".vscode",
|
|
5997
|
-
".vs"
|
|
6132
|
+
".vs",
|
|
6133
|
+
".npm",
|
|
6134
|
+
".yarn",
|
|
6135
|
+
".nyc_output",
|
|
6136
|
+
// Credential directories are invariant exclusions. Their contents often
|
|
6137
|
+
// have ordinary-looking names (config, credentials, known_hosts), so a
|
|
6138
|
+
// filename-only filter is not sufficient.
|
|
6139
|
+
".ssh",
|
|
6140
|
+
".aws",
|
|
6141
|
+
".gnupg",
|
|
6142
|
+
".kube",
|
|
6143
|
+
".docker"
|
|
5998
6144
|
]);
|
|
5999
6145
|
IGNORE_FILES = /* @__PURE__ */ new Set([
|
|
6000
6146
|
".DS_Store",
|
|
@@ -6009,6 +6155,28 @@ var init_files = __esm({
|
|
|
6009
6155
|
"Gemfile.lock",
|
|
6010
6156
|
"composer.lock"
|
|
6011
6157
|
]);
|
|
6158
|
+
SECRET_FILENAMES = /* @__PURE__ */ new Set([
|
|
6159
|
+
".npmrc",
|
|
6160
|
+
".pypirc",
|
|
6161
|
+
".netrc",
|
|
6162
|
+
".pgpass",
|
|
6163
|
+
".git-credentials",
|
|
6164
|
+
"id_rsa",
|
|
6165
|
+
"id_ed25519",
|
|
6166
|
+
"id_ecdsa",
|
|
6167
|
+
"known_hosts",
|
|
6168
|
+
"credentials.json",
|
|
6169
|
+
"serviceaccountkey.json",
|
|
6170
|
+
"auth.json"
|
|
6171
|
+
]);
|
|
6172
|
+
SECRET_FILE_SUFFIXES = [".pem", ".key", ".p12", ".pfx", ".jks"];
|
|
6173
|
+
ENV_TEMPLATE_ALLOWLIST2 = /* @__PURE__ */ new Set([
|
|
6174
|
+
".env.example",
|
|
6175
|
+
".env.sample",
|
|
6176
|
+
".env.template",
|
|
6177
|
+
".env.dist",
|
|
6178
|
+
".env.defaults"
|
|
6179
|
+
]);
|
|
6012
6180
|
MAX_FILE_SIZE = 5 * 1024 * 1024;
|
|
6013
6181
|
MAX_BATCH_BYTES = 1024 * 1024;
|
|
6014
6182
|
LARGE_FILE_THRESHOLD = 2 * 1024 * 1024;
|
|
@@ -8528,10 +8696,14 @@ function extractIndexTimestamp(result) {
|
|
|
8528
8696
|
function apiResultReportsIndexed(result) {
|
|
8529
8697
|
const candidates = candidateObjects(result);
|
|
8530
8698
|
const indexed = readBoolean(candidates, "indexed");
|
|
8531
|
-
if (indexed
|
|
8532
|
-
return
|
|
8699
|
+
if (indexed === true) {
|
|
8700
|
+
return true;
|
|
8533
8701
|
}
|
|
8534
8702
|
const indexedFiles = readNumber(candidates, ["indexed_files", "indexed_file_count"]) ?? 0;
|
|
8703
|
+
const projectIndexState = readString(candidates, "project_index_state")?.toLowerCase();
|
|
8704
|
+
if (indexed === false && projectIndexState !== "ready" && projectIndexState !== "completed") {
|
|
8705
|
+
return false;
|
|
8706
|
+
}
|
|
8535
8707
|
if (indexedFiles > 0) {
|
|
8536
8708
|
return true;
|
|
8537
8709
|
}
|
|
@@ -8544,6 +8716,28 @@ function apiResultReportsIndexed(result) {
|
|
|
8544
8716
|
}
|
|
8545
8717
|
return false;
|
|
8546
8718
|
}
|
|
8719
|
+
function apiIndexResultIsSkipped(result) {
|
|
8720
|
+
const status = readString(candidateObjects(result), "status")?.toLowerCase();
|
|
8721
|
+
return status === "skipped";
|
|
8722
|
+
}
|
|
8723
|
+
function extractIndexedFileCount(result) {
|
|
8724
|
+
const count = readNumber(candidateObjects(result), ["indexed_files", "indexed_file_count"]);
|
|
8725
|
+
return count !== void 0 && count > 0 ? count : void 0;
|
|
8726
|
+
}
|
|
8727
|
+
function classifyRemoteIndexState(result) {
|
|
8728
|
+
if (apiResultReportsIndexed(result)) return "server_index_ready";
|
|
8729
|
+
if (apiResultIsIndexing(result)) return "server_indexing";
|
|
8730
|
+
return "requires_sync_bridge";
|
|
8731
|
+
}
|
|
8732
|
+
function apiAllowsPathIngest(apiUrl, explicitOptIn = false) {
|
|
8733
|
+
if (explicitOptIn) return true;
|
|
8734
|
+
try {
|
|
8735
|
+
const hostname2 = new URL(apiUrl).hostname.toLowerCase();
|
|
8736
|
+
return hostname2 === "localhost" || hostname2 === "127.0.0.1" || hostname2 === "::1" || hostname2 === "[::1]";
|
|
8737
|
+
} catch {
|
|
8738
|
+
return false;
|
|
8739
|
+
}
|
|
8740
|
+
}
|
|
8547
8741
|
function apiResultIsIndexing(result) {
|
|
8548
8742
|
const candidates = candidateObjects(result);
|
|
8549
8743
|
const projectIndexState = readString(candidates, "project_index_state")?.toLowerCase();
|
|
@@ -10334,6 +10528,11 @@ var init_client = __esm({
|
|
|
10334
10528
|
};
|
|
10335
10529
|
}
|
|
10336
10530
|
async ingestFromPath(projectId, pathStr, opts) {
|
|
10531
|
+
if (!this.canDelegatePathIngest()) {
|
|
10532
|
+
throw new Error(
|
|
10533
|
+
"Hosted APIs cannot read workstation paths. Index from a process with local checkout access or use the ContextStream sync bridge/editor hooks."
|
|
10534
|
+
);
|
|
10535
|
+
}
|
|
10337
10536
|
return request(this.config, `/projects/${projectId}/files/ingest-from-path`, {
|
|
10338
10537
|
body: {
|
|
10339
10538
|
path: pathStr,
|
|
@@ -10343,6 +10542,12 @@ var init_client = __esm({
|
|
|
10343
10542
|
}
|
|
10344
10543
|
});
|
|
10345
10544
|
}
|
|
10545
|
+
canDelegatePathIngest() {
|
|
10546
|
+
return apiAllowsPathIngest(
|
|
10547
|
+
this.config.apiUrl,
|
|
10548
|
+
process.env.CONTEXTSTREAM_ALLOW_API_PATH_INGEST === "1"
|
|
10549
|
+
);
|
|
10550
|
+
}
|
|
10346
10551
|
/**
|
|
10347
10552
|
* High-level local ingest: reads files from disk, applies SHA-256 content
|
|
10348
10553
|
* hash filtering, batches, and sends to the API with cooldown/status handling.
|
|
@@ -10365,6 +10570,9 @@ var init_client = __esm({
|
|
|
10365
10570
|
let apiSkipped = 0;
|
|
10366
10571
|
let abortedEarly = false;
|
|
10367
10572
|
let lastStatus = "completed";
|
|
10573
|
+
let failedBatches = 0;
|
|
10574
|
+
let failedFiles = 0;
|
|
10575
|
+
let lastError;
|
|
10368
10576
|
const fileSource = sinceTimestamp ? readChangedFilesInBatches(rootPath, sinceTimestamp, {
|
|
10369
10577
|
batchSize: INGEST_MAX_BATCH_FILES,
|
|
10370
10578
|
maxFilesPerBatch: INGEST_MAX_BATCH_FILES,
|
|
@@ -10377,19 +10585,21 @@ var init_client = __esm({
|
|
|
10377
10585
|
for await (const batch of fileSource) {
|
|
10378
10586
|
if (maxFiles !== void 0 && totalFiles >= maxFiles) break;
|
|
10379
10587
|
const filteredBatch = [];
|
|
10588
|
+
const changedHashes = /* @__PURE__ */ new Map();
|
|
10380
10589
|
for (const file of batch) {
|
|
10381
10590
|
if (maxFiles !== void 0 && totalFiles >= maxFiles) break;
|
|
10382
10591
|
totalFiles++;
|
|
10383
10592
|
const hash = sha256Hex(file.content);
|
|
10384
|
-
newHashes.set(file.path, hash);
|
|
10385
10593
|
if (!force) {
|
|
10386
10594
|
const oldHash = oldHashes.get(file.path);
|
|
10387
10595
|
if (oldHash === hash) {
|
|
10596
|
+
newHashes.set(file.path, hash);
|
|
10388
10597
|
filesSkippedByHash++;
|
|
10389
10598
|
continue;
|
|
10390
10599
|
}
|
|
10391
10600
|
}
|
|
10392
10601
|
filteredBatch.push(file);
|
|
10602
|
+
changedHashes.set(file.path, hash);
|
|
10393
10603
|
}
|
|
10394
10604
|
if (filteredBatch.length === 0) continue;
|
|
10395
10605
|
try {
|
|
@@ -10406,6 +10616,11 @@ var init_client = __esm({
|
|
|
10406
10616
|
abortedEarly = true;
|
|
10407
10617
|
break;
|
|
10408
10618
|
}
|
|
10619
|
+
if (data.status !== "partial") {
|
|
10620
|
+
for (const [filePath, hash] of changedHashes) newHashes.set(filePath, hash);
|
|
10621
|
+
}
|
|
10622
|
+
} else {
|
|
10623
|
+
for (const [filePath, hash] of changedHashes) newHashes.set(filePath, hash);
|
|
10409
10624
|
}
|
|
10410
10625
|
} catch (e) {
|
|
10411
10626
|
console.error(`[ContextStream] Batch ingest error (attempt 1):`, e);
|
|
@@ -10420,14 +10635,22 @@ var init_client = __esm({
|
|
|
10420
10635
|
abortedEarly = true;
|
|
10421
10636
|
break;
|
|
10422
10637
|
}
|
|
10638
|
+
if (data.status !== "partial") {
|
|
10639
|
+
for (const [filePath, hash] of changedHashes) newHashes.set(filePath, hash);
|
|
10640
|
+
}
|
|
10641
|
+
} else {
|
|
10642
|
+
for (const [filePath, hash] of changedHashes) newHashes.set(filePath, hash);
|
|
10423
10643
|
}
|
|
10424
10644
|
} catch (retryErr) {
|
|
10425
10645
|
console.error(`[ContextStream] Batch ingest retry failed:`, retryErr);
|
|
10646
|
+
failedBatches++;
|
|
10647
|
+
failedFiles += filteredBatch.length;
|
|
10648
|
+
lastError = retryErr instanceof Error ? retryErr.message : String(retryErr);
|
|
10426
10649
|
}
|
|
10427
10650
|
}
|
|
10428
10651
|
}
|
|
10429
10652
|
writeHashManifest(projectId, newHashes);
|
|
10430
|
-
const status = abortedEarly ? lastStatus === "daily_limit_exceeded" ? "daily_limit" : "cooldown" : "success";
|
|
10653
|
+
const status = abortedEarly ? lastStatus === "daily_limit_exceeded" ? "daily_limit" : "cooldown" : failedBatches > 0 && filesIndexed === 0 && totalFiles - filesSkippedByHash > 0 ? "error" : failedBatches > 0 || lastStatus === "partial" ? "partial" : "success";
|
|
10431
10654
|
return {
|
|
10432
10655
|
totalFiles,
|
|
10433
10656
|
filesChanged: totalFiles - filesSkippedByHash,
|
|
@@ -10435,7 +10658,10 @@ var init_client = __esm({
|
|
|
10435
10658
|
filesSkipped: filesSkippedByHash,
|
|
10436
10659
|
apiSkipped,
|
|
10437
10660
|
status,
|
|
10438
|
-
abortedEarly
|
|
10661
|
+
abortedEarly,
|
|
10662
|
+
failedBatches,
|
|
10663
|
+
failedFiles,
|
|
10664
|
+
lastError
|
|
10439
10665
|
};
|
|
10440
10666
|
}
|
|
10441
10667
|
/**
|
|
@@ -11019,6 +11245,9 @@ var init_client = __esm({
|
|
|
11019
11245
|
rootPath: rootPathCopy,
|
|
11020
11246
|
maxFiles: AUTO_INDEX_FILE_CAP
|
|
11021
11247
|
});
|
|
11248
|
+
if (result.status === "error") {
|
|
11249
|
+
throw new Error(result.lastError || "all changed-file ingest batches failed");
|
|
11250
|
+
}
|
|
11022
11251
|
console.error(
|
|
11023
11252
|
`[ContextStream] Background indexing completed for ${rootPathCopy}: ${result.filesIndexed} indexed, ${result.filesSkipped} unchanged`
|
|
11024
11253
|
);
|
|
@@ -11194,6 +11423,9 @@ var init_client = __esm({
|
|
|
11194
11423
|
maxFiles: AUTO_INDEX_FILE_CAP,
|
|
11195
11424
|
sinceTimestamp: sinceDate
|
|
11196
11425
|
});
|
|
11426
|
+
if (result.status === "error") {
|
|
11427
|
+
throw new Error(result.lastError || "all changed-file ingest batches failed");
|
|
11428
|
+
}
|
|
11197
11429
|
console.error(
|
|
11198
11430
|
`[ContextStream] ${useIncremental ? "Incremental" : "Full"} index refresh completed for ${rootPathCopy}: ${result.filesIndexed} indexed, ${result.filesSkipped} unchanged`
|
|
11199
11431
|
);
|
|
@@ -12039,6 +12271,9 @@ var init_client = __esm({
|
|
|
12039
12271
|
rootPath: rootPathCopy,
|
|
12040
12272
|
maxFiles: AUTO_INDEX_FILE_CAP
|
|
12041
12273
|
});
|
|
12274
|
+
if (result.status === "error") {
|
|
12275
|
+
throw new Error(result.lastError || "all changed-file ingest batches failed");
|
|
12276
|
+
}
|
|
12042
12277
|
console.error(
|
|
12043
12278
|
`[ContextStream] Auto-indexing completed for ${rootPathCopy}: ${result.filesIndexed} indexed, ${result.filesSkipped} unchanged`
|
|
12044
12279
|
);
|
|
@@ -13383,6 +13618,25 @@ ${context}`;
|
|
|
13383
13618
|
uuidSchema.parse(params.task_id);
|
|
13384
13619
|
return request(this.config, `/tasks/${params.task_id}`, { method: "GET" });
|
|
13385
13620
|
}
|
|
13621
|
+
/** List completed Daily Recaps for a workspace, newest first. */
|
|
13622
|
+
async listDailyRecaps(params) {
|
|
13623
|
+
uuidSchema.parse(params.workspace_id);
|
|
13624
|
+
const query = new URLSearchParams({ workspace_id: params.workspace_id });
|
|
13625
|
+
query.set("limit", String(Math.min(100, Math.max(1, params.limit ?? 30))));
|
|
13626
|
+
return request(this.config, `/recap/history?${query.toString()}`, { method: "GET" });
|
|
13627
|
+
}
|
|
13628
|
+
/** Queue an asynchronous Daily Recap generation for a workspace. */
|
|
13629
|
+
async triggerDailyRecap(params) {
|
|
13630
|
+
uuidSchema.parse(params.workspace_id);
|
|
13631
|
+
return request(this.config, "/recap/generate", {
|
|
13632
|
+
method: "POST",
|
|
13633
|
+
body: { workspace_id: params.workspace_id }
|
|
13634
|
+
});
|
|
13635
|
+
}
|
|
13636
|
+
/** Get the latest public MCP release metadata. */
|
|
13637
|
+
async getMcpVersion() {
|
|
13638
|
+
return request(this.config, "/mcp/version", { method: "GET" });
|
|
13639
|
+
}
|
|
13386
13640
|
/**
|
|
13387
13641
|
* Update an existing task
|
|
13388
13642
|
*/
|
|
@@ -14227,7 +14481,7 @@ function generateAllRuleFiles(options) {
|
|
|
14227
14481
|
}))
|
|
14228
14482
|
).filter((r) => r !== null);
|
|
14229
14483
|
}
|
|
14230
|
-
var DEFAULT_CLAUDE_MCP_SERVER_NAME, RULES_VERSION, CONTEXTSTREAM_TOOL_NAMES, CONTEXTSTREAM_RULES_BOOTSTRAP, CONTEXTSTREAM_RULES_DYNAMIC, CONTEXTSTREAM_RULES_FULL, CONTEXTSTREAM_RULES_MINIMAL, NO_HOOKS_SUPPLEMENT, ANTIGRAVITY_SUPPLEMENT, NO_HOOKS_EDITORS, TEMPLATES;
|
|
14484
|
+
var DEFAULT_CLAUDE_MCP_SERVER_NAME, RULES_VERSION, CONTEXTSTREAM_TOOL_NAMES, FAST_DIRECT_READ_LANE, CANONICAL_HANDOFF_RULES, CONTEXTSTREAM_RULES_BOOTSTRAP, CONTEXTSTREAM_RULES_DYNAMIC, CONTEXTSTREAM_RULES_FULL, CONTEXTSTREAM_RULES_MINIMAL, NO_HOOKS_SUPPLEMENT, ANTIGRAVITY_SUPPLEMENT, NO_HOOKS_EDITORS, TEMPLATES;
|
|
14231
14485
|
var init_rules_templates = __esm({
|
|
14232
14486
|
"src/rules-templates.ts"() {
|
|
14233
14487
|
"use strict";
|
|
@@ -14274,6 +14528,28 @@ var init_rules_templates = __esm({
|
|
|
14274
14528
|
"help"
|
|
14275
14529
|
// Actions: tools, auth, version, editor_rules, enable_bundle
|
|
14276
14530
|
];
|
|
14531
|
+
FAST_DIRECT_READ_LANE = `
|
|
14532
|
+
## Fast Direct-Read Lane
|
|
14533
|
+
|
|
14534
|
+
After the required first-session \`init(...)\` + \`context(...)\`, these exact read-only calls may run without another grounding preamble only when no state-changing tool has run since the last grounding:
|
|
14535
|
+
- \`workspace(action="list"|"get")\`
|
|
14536
|
+
- \`memory(action="list_docs"|"list_events"|"list_todos"|"list_tasks"|"list_transcripts"|"list_nodes"|"list_diagrams")\`
|
|
14537
|
+
- \`help(action="version"|"tools"|"auth")\`
|
|
14538
|
+
- \`project(action="list"|"get"|"index_status")\`
|
|
14539
|
+
- \`reminder(action="list"|"active")\`
|
|
14540
|
+
|
|
14541
|
+
Do not use this lane for recall, decisions, searches, reading a specific record, media queries, or any create/update/delete/index operation; ground those with \`context(...)\` first.
|
|
14542
|
+
`.trim();
|
|
14543
|
+
CANONICAL_HANDOFF_RULES = `
|
|
14544
|
+
## Canonical Agent Handoffs
|
|
14545
|
+
|
|
14546
|
+
When the user asks to create/prepare a handoff, hand work over, or continue in another agent/session, create the durable record immediately with:
|
|
14547
|
+
\`entity(kind="handoff", action="create", body={title, summary, scope, next_steps})\`
|
|
14548
|
+
|
|
14549
|
+
- Add \`to_user_id\` only when the recipient is known; never invent it.
|
|
14550
|
+
- If a portable bundle, capsule, or share link is requested, also create a session capsule and return both artifacts.
|
|
14551
|
+
- \`HANDOFF.md\`, a scratch prompt, a generic doc/event, or prose alone is not the canonical handoff. If a local file is explicitly requested, create the entity first and treat the file as an additional artifact.
|
|
14552
|
+
`.trim();
|
|
14277
14553
|
CONTEXTSTREAM_RULES_BOOTSTRAP = `
|
|
14278
14554
|
<contextstream_rules>
|
|
14279
14555
|
| Message | Required |
|
|
@@ -14297,6 +14573,10 @@ var init_rules_templates = __esm({
|
|
|
14297
14573
|
- "find media" / "search photos/videos/audio/docs" / "what's in this PDF/video/audio?" \u2192 \`media(action="search", query="...", content_types=["document"])\`
|
|
14298
14574
|
- "index media" / "upload asset" / "read this photo/video/audio/PDF" \u2192 \`media(action="index", file_path="...", content_type="image")\` or \`media(action="index", external_url="...", content_type="document")\`
|
|
14299
14575
|
|
|
14576
|
+
${FAST_DIRECT_READ_LANE}
|
|
14577
|
+
|
|
14578
|
+
${CANONICAL_HANDOFF_RULES}
|
|
14579
|
+
|
|
14300
14580
|
## Skills, Docs & Lessons First
|
|
14301
14581
|
|
|
14302
14582
|
Before guessing, improvising, or struggling through a workflow you do not fully know, check whether ContextStream already has guidance for it.
|
|
@@ -14326,12 +14606,12 @@ Before guessing, improvising, or struggling through a workflow you do not fully
|
|
|
14326
14606
|
|
|
14327
14607
|
## Past Sessions Are Queryable \u2014 USE THEM
|
|
14328
14608
|
|
|
14329
|
-
|
|
14609
|
+
When \`context()\` returns fresh, relevant \`[GROUNDING]\`, read those ranked prior-work hits first. If they are sufficient, stop there\u2014do not immediately call \`session(action="recall")\` for the same request. Escalate to recall only when grounding is absent, thin, stale, off-topic, or the user explicitly requests broader history.
|
|
14330
14610
|
|
|
14331
14611
|
Triggers: user says "last time"/"previous"/"yesterday"/"pick up where we left off", the task is clearly a continuation, or you're about to ask a clarifying question whose answer is likely in a prior session.
|
|
14332
14612
|
|
|
14333
14613
|
Exact calls:
|
|
14334
|
-
-
|
|
14614
|
+
- First explicit escalation after insufficient grounding: \`session(action="recall", query="<what you're continuing>")\`
|
|
14335
14615
|
- Chronological list of recent sessions: \`memory(action="list_transcripts", limit=10)\`
|
|
14336
14616
|
- Full-text search across ALL past transcripts: \`memory(action="search_transcripts", query="<keyword>")\`
|
|
14337
14617
|
- Save a snapshot of the current session so the NEXT session can pick up: \`session(action="capture", event_type="session_snapshot", title="...", content="...")\`
|
|
@@ -14357,6 +14637,10 @@ v${RULES_VERSION}
|
|
|
14357
14637
|
|
|
14358
14638
|
Use \`context()\` by default to get task-specific rules, lessons from past mistakes, and relevant decisions.
|
|
14359
14639
|
|
|
14640
|
+
${FAST_DIRECT_READ_LANE}
|
|
14641
|
+
|
|
14642
|
+
${CANONICAL_HANDOFF_RULES}
|
|
14643
|
+
|
|
14360
14644
|
---
|
|
14361
14645
|
|
|
14362
14646
|
## Why Default Context-First
|
|
@@ -14890,6 +15174,10 @@ See full documentation: https://contextstream.io/docs/mcp/tools
|
|
|
14890
15174
|
**HOOKS: \`<system-reminder>\` tags contain instructions \u2014 FOLLOW THEM**
|
|
14891
15175
|
</contextstream_protocol>
|
|
14892
15176
|
|
|
15177
|
+
${FAST_DIRECT_READ_LANE}
|
|
15178
|
+
|
|
15179
|
+
${CANONICAL_HANDOFF_RULES}
|
|
15180
|
+
|
|
14893
15181
|
Rules Version: ${RULES_VERSION}
|
|
14894
15182
|
|
|
14895
15183
|
---
|
|
@@ -15212,7 +15500,7 @@ curl -fsSL https://contextstream.io/scripts/setup.sh | bash
|
|
|
15212
15500
|
irm https://contextstream.io/scripts/setup.ps1 | iex
|
|
15213
15501
|
\`\`\`
|
|
15214
15502
|
|
|
15215
|
-
**npm (requires Node.js
|
|
15503
|
+
**npm (requires Node.js 20+):**
|
|
15216
15504
|
\`\`\`bash
|
|
15217
15505
|
npm install -g @contextstream/mcp-server@latest
|
|
15218
15506
|
\`\`\`
|
|
@@ -15344,14 +15632,14 @@ Full docs: https://contextstream.io/docs/mcp/tools
|
|
|
15344
15632
|
});
|
|
15345
15633
|
|
|
15346
15634
|
// src/credentials.ts
|
|
15347
|
-
import * as
|
|
15348
|
-
import * as
|
|
15349
|
-
import { homedir as
|
|
15635
|
+
import * as fs7 from "node:fs/promises";
|
|
15636
|
+
import * as path8 from "node:path";
|
|
15637
|
+
import { homedir as homedir6 } from "node:os";
|
|
15350
15638
|
function normalizeApiUrl(input) {
|
|
15351
15639
|
return String(input ?? "").trim().replace(/\/+$/, "");
|
|
15352
15640
|
}
|
|
15353
15641
|
function credentialsFilePath() {
|
|
15354
|
-
return
|
|
15642
|
+
return path8.join(homedir6(), ".contextstream", "credentials.json");
|
|
15355
15643
|
}
|
|
15356
15644
|
function isRecord(value) {
|
|
15357
15645
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -15359,7 +15647,7 @@ function isRecord(value) {
|
|
|
15359
15647
|
async function readSavedCredentials() {
|
|
15360
15648
|
const filePath = credentialsFilePath();
|
|
15361
15649
|
try {
|
|
15362
|
-
const raw = await
|
|
15650
|
+
const raw = await fs7.readFile(filePath, "utf8");
|
|
15363
15651
|
const parsed = JSON.parse(raw);
|
|
15364
15652
|
if (!isRecord(parsed)) return null;
|
|
15365
15653
|
const version = parsed.version;
|
|
@@ -15385,7 +15673,7 @@ async function readSavedCredentials() {
|
|
|
15385
15673
|
}
|
|
15386
15674
|
async function writeSavedCredentials(input) {
|
|
15387
15675
|
const filePath = credentialsFilePath();
|
|
15388
|
-
await
|
|
15676
|
+
await fs7.mkdir(path8.dirname(filePath), { recursive: true });
|
|
15389
15677
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
15390
15678
|
const existing = await readSavedCredentials();
|
|
15391
15679
|
const value = {
|
|
@@ -15397,9 +15685,9 @@ async function writeSavedCredentials(input) {
|
|
|
15397
15685
|
updated_at: now
|
|
15398
15686
|
};
|
|
15399
15687
|
const body = JSON.stringify(value, null, 2) + "\n";
|
|
15400
|
-
await
|
|
15688
|
+
await fs7.writeFile(filePath, body, { encoding: "utf8", mode: 384 });
|
|
15401
15689
|
try {
|
|
15402
|
-
await
|
|
15690
|
+
await fs7.chmod(filePath, 384);
|
|
15403
15691
|
} catch {
|
|
15404
15692
|
}
|
|
15405
15693
|
return { path: filePath, value };
|
|
@@ -15416,7 +15704,7 @@ __export(doctor_exports, {
|
|
|
15416
15704
|
runDoctor: () => runDoctor
|
|
15417
15705
|
});
|
|
15418
15706
|
import * as fsSync2 from "node:fs";
|
|
15419
|
-
import * as
|
|
15707
|
+
import * as path9 from "node:path";
|
|
15420
15708
|
async function runDoctor() {
|
|
15421
15709
|
const cwd = process.cwd();
|
|
15422
15710
|
const lines = [`ContextStream MCP doctor v${VERSION}`, ""];
|
|
@@ -15465,7 +15753,7 @@ async function runDoctor() {
|
|
|
15465
15753
|
}
|
|
15466
15754
|
lines.push("", "Local index");
|
|
15467
15755
|
const status = await readIndexStatus();
|
|
15468
|
-
const entry = status.projects?.[
|
|
15756
|
+
const entry = status.projects?.[path9.resolve(cwd)];
|
|
15469
15757
|
if (!entry) {
|
|
15470
15758
|
warn(
|
|
15471
15759
|
"No local index record for this folder.",
|
|
@@ -15507,7 +15795,7 @@ async function runDoctor() {
|
|
|
15507
15795
|
lines.push("", "Editor rules");
|
|
15508
15796
|
let ruleFilesFound = 0;
|
|
15509
15797
|
for (const [editor, template] of Object.entries(TEMPLATES)) {
|
|
15510
|
-
const filePath =
|
|
15798
|
+
const filePath = path9.join(cwd, template.filename);
|
|
15511
15799
|
if (!fsSync2.existsSync(filePath)) continue;
|
|
15512
15800
|
ruleFilesFound += 1;
|
|
15513
15801
|
const content = fsSync2.readFileSync(filePath, "utf-8");
|
|
@@ -15529,8 +15817,8 @@ async function runDoctor() {
|
|
|
15529
15817
|
}
|
|
15530
15818
|
lines.push("", "Hooks");
|
|
15531
15819
|
const hookTargets = [
|
|
15532
|
-
{ name: "Claude Code (project)", file:
|
|
15533
|
-
{ name: "Cursor (project)", file:
|
|
15820
|
+
{ name: "Claude Code (project)", file: path9.join(cwd, ".claude", "settings.json") },
|
|
15821
|
+
{ name: "Cursor (project)", file: path9.join(cwd, ".cursor", "hooks.json") }
|
|
15534
15822
|
];
|
|
15535
15823
|
let hookConfigsFound = 0;
|
|
15536
15824
|
for (const target of hookTargets) {
|
|
@@ -15573,12 +15861,13 @@ var init_doctor = __esm({
|
|
|
15573
15861
|
var setup_exports = {};
|
|
15574
15862
|
__export(setup_exports, {
|
|
15575
15863
|
__setupTestUtils: () => __setupTestUtils,
|
|
15864
|
+
isBroadProjectPath: () => isBroadProjectPath,
|
|
15576
15865
|
runIndexCommand: () => runIndexCommand,
|
|
15577
15866
|
runSetupWizard: () => runSetupWizard
|
|
15578
15867
|
});
|
|
15579
|
-
import * as
|
|
15580
|
-
import * as
|
|
15581
|
-
import { homedir as
|
|
15868
|
+
import * as fs8 from "node:fs/promises";
|
|
15869
|
+
import * as path10 from "node:path";
|
|
15870
|
+
import { homedir as homedir7 } from "node:os";
|
|
15582
15871
|
import { stdin, stdout } from "node:process";
|
|
15583
15872
|
import { createInterface } from "node:readline/promises";
|
|
15584
15873
|
function supportsProjectMcpConfig(editor) {
|
|
@@ -15608,40 +15897,40 @@ function parseNumberList(input, max) {
|
|
|
15608
15897
|
}
|
|
15609
15898
|
async function fileExists(filePath) {
|
|
15610
15899
|
try {
|
|
15611
|
-
await
|
|
15900
|
+
await fs8.stat(filePath);
|
|
15612
15901
|
return true;
|
|
15613
15902
|
} catch {
|
|
15614
15903
|
return false;
|
|
15615
15904
|
}
|
|
15616
15905
|
}
|
|
15617
|
-
function
|
|
15618
|
-
return `${
|
|
15906
|
+
function wrapWithMarkers(content) {
|
|
15907
|
+
return `${CONTEXTSTREAM_START_MARKER}
|
|
15619
15908
|
${content.trim()}
|
|
15620
|
-
${
|
|
15909
|
+
${CONTEXTSTREAM_END_MARKER}`;
|
|
15621
15910
|
}
|
|
15622
|
-
function
|
|
15911
|
+
function isLegacyContextStreamRules(content) {
|
|
15623
15912
|
const lower = content.toLowerCase();
|
|
15624
15913
|
if (!lower.includes("contextstream")) return false;
|
|
15625
|
-
if (!
|
|
15914
|
+
if (!LEGACY_CONTEXTSTREAM_HINTS.some((hint) => lower.includes(hint))) return false;
|
|
15626
15915
|
const headingRegex = /^#{1,6}\s+(.+)$/gm;
|
|
15627
15916
|
let hasHeading = false;
|
|
15628
15917
|
let match;
|
|
15629
15918
|
while ((match = headingRegex.exec(content)) !== null) {
|
|
15630
15919
|
hasHeading = true;
|
|
15631
15920
|
const heading = match[1].trim().toLowerCase();
|
|
15632
|
-
const allowed =
|
|
15921
|
+
const allowed = LEGACY_CONTEXTSTREAM_ALLOWED_HEADINGS.some(
|
|
15633
15922
|
(prefix) => heading.startsWith(prefix)
|
|
15634
15923
|
);
|
|
15635
15924
|
if (!allowed) return false;
|
|
15636
15925
|
}
|
|
15637
15926
|
return hasHeading;
|
|
15638
15927
|
}
|
|
15639
|
-
function
|
|
15928
|
+
function isContextStreamPreamble(line) {
|
|
15640
15929
|
const trimmed = line.trim();
|
|
15641
15930
|
if (!trimmed) return false;
|
|
15642
|
-
return
|
|
15931
|
+
return CONTEXTSTREAM_PREAMBLE_PATTERNS.some((pattern) => pattern.test(trimmed));
|
|
15643
15932
|
}
|
|
15644
|
-
function
|
|
15933
|
+
function findContextStreamHeading(lines) {
|
|
15645
15934
|
const headingRegex = /^(#{1,6})\s+(.+)$/;
|
|
15646
15935
|
for (let i = 0; i < lines.length; i += 1) {
|
|
15647
15936
|
const match = headingRegex.exec(lines[i]);
|
|
@@ -15652,7 +15941,7 @@ function findContextStreamHeading2(lines) {
|
|
|
15652
15941
|
}
|
|
15653
15942
|
return null;
|
|
15654
15943
|
}
|
|
15655
|
-
function
|
|
15944
|
+
function findSectionEnd(lines, startLine, level) {
|
|
15656
15945
|
const headingRegex = /^(#{1,6})\s+(.+)$/;
|
|
15657
15946
|
for (let i = startLine + 1; i < lines.length; i += 1) {
|
|
15658
15947
|
const match = headingRegex.exec(lines[i]);
|
|
@@ -15661,16 +15950,16 @@ function findSectionEnd2(lines, startLine, level) {
|
|
|
15661
15950
|
}
|
|
15662
15951
|
return lines.length;
|
|
15663
15952
|
}
|
|
15664
|
-
function
|
|
15953
|
+
function extractContextStreamBlock(content) {
|
|
15665
15954
|
const lines = content.split(/\r?\n/);
|
|
15666
|
-
const heading =
|
|
15955
|
+
const heading = findContextStreamHeading(lines);
|
|
15667
15956
|
if (!heading) return content.trim();
|
|
15668
|
-
const endLine =
|
|
15957
|
+
const endLine = findSectionEnd(lines, heading.index, heading.level);
|
|
15669
15958
|
return lines.slice(heading.index, endLine).join("\n").trim();
|
|
15670
15959
|
}
|
|
15671
|
-
function
|
|
15960
|
+
function findLegacyContextStreamSection(content) {
|
|
15672
15961
|
const lines = content.split(/\r?\n/);
|
|
15673
|
-
const heading =
|
|
15962
|
+
const heading = findContextStreamHeading(lines);
|
|
15674
15963
|
if (!heading) return null;
|
|
15675
15964
|
let startLine = heading.index;
|
|
15676
15965
|
for (let i = heading.index - 1; i >= 0; i -= 1) {
|
|
@@ -15679,39 +15968,39 @@ function findLegacyContextStreamSection2(content) {
|
|
|
15679
15968
|
startLine = i;
|
|
15680
15969
|
continue;
|
|
15681
15970
|
}
|
|
15682
|
-
if (
|
|
15971
|
+
if (isContextStreamPreamble(line)) {
|
|
15683
15972
|
startLine = i;
|
|
15684
15973
|
continue;
|
|
15685
15974
|
}
|
|
15686
15975
|
break;
|
|
15687
15976
|
}
|
|
15688
|
-
const endLine =
|
|
15977
|
+
const endLine = findSectionEnd(lines, heading.index, heading.level);
|
|
15689
15978
|
return { startLine, endLine, contextLine: heading.index };
|
|
15690
15979
|
}
|
|
15691
|
-
function
|
|
15980
|
+
function blockHasPreamble(block) {
|
|
15692
15981
|
const lines = block.split(/\r?\n/);
|
|
15693
15982
|
for (const line of lines) {
|
|
15694
15983
|
const trimmed = line.trim();
|
|
15695
15984
|
if (!trimmed) continue;
|
|
15696
|
-
if (
|
|
15985
|
+
if (isContextStreamPreamble(trimmed)) return true;
|
|
15697
15986
|
if (/^#{1,6}\s+/.test(trimmed)) return false;
|
|
15698
15987
|
}
|
|
15699
15988
|
return false;
|
|
15700
15989
|
}
|
|
15701
|
-
function
|
|
15702
|
-
const fullWrapped =
|
|
15703
|
-
const blockWrapped =
|
|
15704
|
-
const startIdx = existing.indexOf(
|
|
15705
|
-
const endIdx = existing.indexOf(
|
|
15990
|
+
function replaceContextStreamBlock(existing, content) {
|
|
15991
|
+
const fullWrapped = wrapWithMarkers(content);
|
|
15992
|
+
const blockWrapped = wrapWithMarkers(extractContextStreamBlock(content));
|
|
15993
|
+
const startIdx = existing.indexOf(CONTEXTSTREAM_START_MARKER);
|
|
15994
|
+
const endIdx = existing.indexOf(CONTEXTSTREAM_END_MARKER);
|
|
15706
15995
|
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
15707
|
-
const existingBlock = existing.slice(startIdx +
|
|
15708
|
-
const replacement =
|
|
15996
|
+
const existingBlock = existing.slice(startIdx + CONTEXTSTREAM_START_MARKER.length, endIdx);
|
|
15997
|
+
const replacement = blockHasPreamble(existingBlock) ? fullWrapped : blockWrapped;
|
|
15709
15998
|
const before = existing.substring(0, startIdx).trimEnd();
|
|
15710
|
-
const after = existing.substring(endIdx +
|
|
15999
|
+
const after = existing.substring(endIdx + CONTEXTSTREAM_END_MARKER.length).trimStart();
|
|
15711
16000
|
const merged = [before, replacement, after].filter((part) => part.length > 0).join("\n\n");
|
|
15712
16001
|
return { content: merged.trim() + "\n", status: "updated" };
|
|
15713
16002
|
}
|
|
15714
|
-
const legacy =
|
|
16003
|
+
const legacy = findLegacyContextStreamSection(existing);
|
|
15715
16004
|
if (legacy) {
|
|
15716
16005
|
const lines = existing.split(/\r?\n/);
|
|
15717
16006
|
const before = lines.slice(0, legacy.startLine).join("\n").trimEnd();
|
|
@@ -15720,54 +16009,54 @@ function replaceContextStreamBlock2(existing, content) {
|
|
|
15720
16009
|
const merged = [before, replacement, after].filter((part) => part.length > 0).join("\n\n");
|
|
15721
16010
|
return { content: merged.trim() + "\n", status: "updated" };
|
|
15722
16011
|
}
|
|
15723
|
-
if (
|
|
16012
|
+
if (isLegacyContextStreamRules(existing)) {
|
|
15724
16013
|
return { content: fullWrapped + "\n", status: "updated" };
|
|
15725
16014
|
}
|
|
15726
16015
|
const appended = existing.trimEnd() + "\n\n" + blockWrapped + "\n";
|
|
15727
16016
|
return { content: appended, status: "appended" };
|
|
15728
16017
|
}
|
|
15729
16018
|
async function upsertTextFile(filePath, content, _marker) {
|
|
15730
|
-
await
|
|
16019
|
+
await fs8.mkdir(path10.dirname(filePath), { recursive: true });
|
|
15731
16020
|
const exists = await fileExists(filePath);
|
|
15732
|
-
const wrappedContent =
|
|
16021
|
+
const wrappedContent = wrapWithMarkers(content);
|
|
15733
16022
|
if (!exists) {
|
|
15734
|
-
await
|
|
16023
|
+
await fs8.writeFile(filePath, wrappedContent + "\n", "utf8");
|
|
15735
16024
|
return "created";
|
|
15736
16025
|
}
|
|
15737
|
-
const existing = await
|
|
16026
|
+
const existing = await fs8.readFile(filePath, "utf8").catch(() => "");
|
|
15738
16027
|
if (!existing.trim()) {
|
|
15739
|
-
await
|
|
16028
|
+
await fs8.writeFile(filePath, wrappedContent + "\n", "utf8");
|
|
15740
16029
|
return "updated";
|
|
15741
16030
|
}
|
|
15742
|
-
const replaced =
|
|
15743
|
-
await
|
|
16031
|
+
const replaced = replaceContextStreamBlock(existing, content);
|
|
16032
|
+
await fs8.writeFile(filePath, replaced.content, "utf8");
|
|
15744
16033
|
return replaced.status;
|
|
15745
16034
|
}
|
|
15746
16035
|
function globalRulesPathForEditor(editor) {
|
|
15747
|
-
const home =
|
|
16036
|
+
const home = homedir7();
|
|
15748
16037
|
switch (editor) {
|
|
15749
16038
|
case "codex":
|
|
15750
|
-
return
|
|
16039
|
+
return path10.join(home, ".codex", "AGENTS.md");
|
|
15751
16040
|
case "copilot":
|
|
15752
16041
|
return null;
|
|
15753
16042
|
case "opencode":
|
|
15754
|
-
return
|
|
16043
|
+
return path10.join(home, ".opencode", "AGENTS.md");
|
|
15755
16044
|
case "claude":
|
|
15756
|
-
return
|
|
16045
|
+
return path10.join(home, ".claude", "CLAUDE.md");
|
|
15757
16046
|
case "cline":
|
|
15758
|
-
return
|
|
16047
|
+
return path10.join(home, "Documents", "Cline", "Rules", "contextstream.md");
|
|
15759
16048
|
case "kilo":
|
|
15760
|
-
return
|
|
16049
|
+
return path10.join(kiloConfigDir(), "rules", "contextstream.md");
|
|
15761
16050
|
case "roo":
|
|
15762
|
-
return
|
|
16051
|
+
return path10.join(home, ".roo", "rules", "contextstream.md");
|
|
15763
16052
|
case "aider":
|
|
15764
|
-
return
|
|
16053
|
+
return path10.join(home, ".aider.conf.yml");
|
|
15765
16054
|
case "antigravity":
|
|
15766
|
-
return
|
|
16055
|
+
return path10.join(home, ".gemini", "GEMINI.md");
|
|
15767
16056
|
case "cursor":
|
|
15768
16057
|
return null;
|
|
15769
16058
|
case "windsurf":
|
|
15770
|
-
return
|
|
16059
|
+
return path10.join(home, ".codeium", "windsurf", "memories", "global_rules.md");
|
|
15771
16060
|
default:
|
|
15772
16061
|
return null;
|
|
15773
16062
|
}
|
|
@@ -15779,104 +16068,104 @@ async function anyPathExists(paths) {
|
|
|
15779
16068
|
return false;
|
|
15780
16069
|
}
|
|
15781
16070
|
async function isCodexInstalled() {
|
|
15782
|
-
const home =
|
|
16071
|
+
const home = homedir7();
|
|
15783
16072
|
const envHome = process.env.CODEX_HOME;
|
|
15784
16073
|
const candidates = [
|
|
15785
16074
|
envHome,
|
|
15786
|
-
|
|
15787
|
-
|
|
15788
|
-
|
|
16075
|
+
path10.join(home, ".codex"),
|
|
16076
|
+
path10.join(home, ".codex", "config.toml"),
|
|
16077
|
+
path10.join(home, ".config", "codex")
|
|
15789
16078
|
].filter((candidate) => Boolean(candidate));
|
|
15790
16079
|
return anyPathExists(candidates);
|
|
15791
16080
|
}
|
|
15792
16081
|
function openCodeConfigPath() {
|
|
15793
|
-
const home =
|
|
16082
|
+
const home = homedir7();
|
|
15794
16083
|
const xdgConfigHome = process.env.XDG_CONFIG_HOME;
|
|
15795
16084
|
if (process.platform === "win32") {
|
|
15796
|
-
const appData = process.env.APPDATA ||
|
|
15797
|
-
return
|
|
16085
|
+
const appData = process.env.APPDATA || path10.join(home, "AppData", "Roaming");
|
|
16086
|
+
return path10.join(appData, "opencode", "opencode.json");
|
|
15798
16087
|
}
|
|
15799
|
-
const configRoot = xdgConfigHome ||
|
|
15800
|
-
return
|
|
16088
|
+
const configRoot = xdgConfigHome || path10.join(home, ".config");
|
|
16089
|
+
return path10.join(configRoot, "opencode", "opencode.json");
|
|
15801
16090
|
}
|
|
15802
16091
|
async function isOpenCodeInstalled() {
|
|
15803
16092
|
const configPath = openCodeConfigPath();
|
|
15804
|
-
const configDir =
|
|
15805
|
-
const home =
|
|
16093
|
+
const configDir = path10.dirname(configPath);
|
|
16094
|
+
const home = homedir7();
|
|
15806
16095
|
const candidates = [
|
|
15807
16096
|
configDir,
|
|
15808
16097
|
configPath,
|
|
15809
|
-
|
|
15810
|
-
|
|
16098
|
+
path10.join(home, ".bun", "bin", "opencode"),
|
|
16099
|
+
path10.join(home, ".local", "bin", "opencode")
|
|
15811
16100
|
];
|
|
15812
16101
|
return anyPathExists(candidates);
|
|
15813
16102
|
}
|
|
15814
16103
|
async function isClaudeInstalled() {
|
|
15815
|
-
const home =
|
|
15816
|
-
const candidates = [
|
|
16104
|
+
const home = homedir7();
|
|
16105
|
+
const candidates = [path10.join(home, ".claude"), path10.join(home, ".config", "claude")];
|
|
15817
16106
|
const desktopConfig = claudeDesktopConfigPath();
|
|
15818
16107
|
if (desktopConfig) candidates.push(desktopConfig);
|
|
15819
16108
|
if (process.platform === "darwin") {
|
|
15820
|
-
candidates.push(
|
|
16109
|
+
candidates.push(path10.join(home, "Library", "Application Support", "Claude"));
|
|
15821
16110
|
} else if (process.platform === "win32") {
|
|
15822
16111
|
const appData = process.env.APPDATA;
|
|
15823
|
-
if (appData) candidates.push(
|
|
16112
|
+
if (appData) candidates.push(path10.join(appData, "Claude"));
|
|
15824
16113
|
}
|
|
15825
16114
|
return anyPathExists(candidates);
|
|
15826
16115
|
}
|
|
15827
16116
|
async function isClineInstalled() {
|
|
15828
|
-
const home =
|
|
16117
|
+
const home = homedir7();
|
|
15829
16118
|
const candidates = [
|
|
15830
|
-
|
|
15831
|
-
|
|
15832
|
-
|
|
16119
|
+
path10.join(home, "Documents", "Cline"),
|
|
16120
|
+
path10.join(home, ".cline"),
|
|
16121
|
+
path10.join(home, ".config", "cline")
|
|
15833
16122
|
];
|
|
15834
16123
|
return anyPathExists(candidates);
|
|
15835
16124
|
}
|
|
15836
16125
|
async function isCopilotInstalled() {
|
|
15837
|
-
const home =
|
|
16126
|
+
const home = homedir7();
|
|
15838
16127
|
const candidates = [
|
|
15839
|
-
|
|
15840
|
-
|
|
15841
|
-
|
|
15842
|
-
|
|
16128
|
+
path10.join(home, ".copilot"),
|
|
16129
|
+
path10.join(home, ".config", "github-copilot"),
|
|
16130
|
+
path10.join(home, ".vscode", "extensions", "github.copilot-chat"),
|
|
16131
|
+
path10.join(home, ".vscode", "extensions", "github.copilot")
|
|
15843
16132
|
];
|
|
15844
16133
|
return anyPathExists(candidates);
|
|
15845
16134
|
}
|
|
15846
16135
|
async function isKiloInstalled() {
|
|
15847
|
-
const home =
|
|
16136
|
+
const home = homedir7();
|
|
15848
16137
|
const candidates = [
|
|
15849
16138
|
kiloConfigDir(),
|
|
15850
|
-
|
|
15851
|
-
|
|
16139
|
+
path10.join(home, ".kilocode"),
|
|
16140
|
+
path10.join(home, ".config", "kilocode")
|
|
15852
16141
|
];
|
|
15853
16142
|
return anyPathExists(candidates);
|
|
15854
16143
|
}
|
|
15855
16144
|
async function isRooInstalled() {
|
|
15856
|
-
const home =
|
|
15857
|
-
const candidates = [
|
|
16145
|
+
const home = homedir7();
|
|
16146
|
+
const candidates = [path10.join(home, ".roo"), path10.join(home, ".config", "roo")];
|
|
15858
16147
|
return anyPathExists(candidates);
|
|
15859
16148
|
}
|
|
15860
16149
|
async function isAiderInstalled() {
|
|
15861
|
-
const home =
|
|
15862
|
-
const candidates = [
|
|
16150
|
+
const home = homedir7();
|
|
16151
|
+
const candidates = [path10.join(home, ".aider.conf.yml"), path10.join(home, ".config", "aider")];
|
|
15863
16152
|
return anyPathExists(candidates);
|
|
15864
16153
|
}
|
|
15865
16154
|
async function isCursorInstalled() {
|
|
15866
|
-
const home =
|
|
15867
|
-
const candidates = [
|
|
16155
|
+
const home = homedir7();
|
|
16156
|
+
const candidates = [path10.join(home, ".cursor")];
|
|
15868
16157
|
if (process.platform === "darwin") {
|
|
15869
16158
|
candidates.push("/Applications/Cursor.app");
|
|
15870
|
-
candidates.push(
|
|
15871
|
-
candidates.push(
|
|
16159
|
+
candidates.push(path10.join(home, "Applications", "Cursor.app"));
|
|
16160
|
+
candidates.push(path10.join(home, "Library", "Application Support", "Cursor"));
|
|
15872
16161
|
} else if (process.platform === "win32") {
|
|
15873
16162
|
const localApp = process.env.LOCALAPPDATA;
|
|
15874
16163
|
const programFiles = process.env.ProgramFiles;
|
|
15875
16164
|
const programFilesX86 = process.env["ProgramFiles(x86)"];
|
|
15876
|
-
if (localApp) candidates.push(
|
|
15877
|
-
if (localApp) candidates.push(
|
|
15878
|
-
if (programFiles) candidates.push(
|
|
15879
|
-
if (programFilesX86) candidates.push(
|
|
16165
|
+
if (localApp) candidates.push(path10.join(localApp, "Programs", "Cursor", "Cursor.exe"));
|
|
16166
|
+
if (localApp) candidates.push(path10.join(localApp, "Cursor", "Cursor.exe"));
|
|
16167
|
+
if (programFiles) candidates.push(path10.join(programFiles, "Cursor", "Cursor.exe"));
|
|
16168
|
+
if (programFilesX86) candidates.push(path10.join(programFilesX86, "Cursor", "Cursor.exe"));
|
|
15880
16169
|
} else {
|
|
15881
16170
|
candidates.push("/usr/bin/cursor");
|
|
15882
16171
|
candidates.push("/usr/local/bin/cursor");
|
|
@@ -15886,20 +16175,20 @@ async function isCursorInstalled() {
|
|
|
15886
16175
|
return anyPathExists(candidates);
|
|
15887
16176
|
}
|
|
15888
16177
|
async function isWindsurfInstalled() {
|
|
15889
|
-
const home =
|
|
15890
|
-
const candidates = [
|
|
16178
|
+
const home = homedir7();
|
|
16179
|
+
const candidates = [path10.join(home, ".codeium", "windsurf")];
|
|
15891
16180
|
if (process.platform === "darwin") {
|
|
15892
16181
|
candidates.push("/Applications/Windsurf.app");
|
|
15893
|
-
candidates.push(
|
|
15894
|
-
candidates.push(
|
|
16182
|
+
candidates.push(path10.join(home, "Applications", "Windsurf.app"));
|
|
16183
|
+
candidates.push(path10.join(home, "Library", "Application Support", "Windsurf"));
|
|
15895
16184
|
} else if (process.platform === "win32") {
|
|
15896
16185
|
const localApp = process.env.LOCALAPPDATA;
|
|
15897
16186
|
const programFiles = process.env.ProgramFiles;
|
|
15898
16187
|
const programFilesX86 = process.env["ProgramFiles(x86)"];
|
|
15899
|
-
if (localApp) candidates.push(
|
|
15900
|
-
if (localApp) candidates.push(
|
|
15901
|
-
if (programFiles) candidates.push(
|
|
15902
|
-
if (programFilesX86) candidates.push(
|
|
16188
|
+
if (localApp) candidates.push(path10.join(localApp, "Programs", "Windsurf", "Windsurf.exe"));
|
|
16189
|
+
if (localApp) candidates.push(path10.join(localApp, "Windsurf", "Windsurf.exe"));
|
|
16190
|
+
if (programFiles) candidates.push(path10.join(programFiles, "Windsurf", "Windsurf.exe"));
|
|
16191
|
+
if (programFilesX86) candidates.push(path10.join(programFilesX86, "Windsurf", "Windsurf.exe"));
|
|
15903
16192
|
} else {
|
|
15904
16193
|
candidates.push("/usr/bin/windsurf");
|
|
15905
16194
|
candidates.push("/usr/local/bin/windsurf");
|
|
@@ -15909,22 +16198,22 @@ async function isWindsurfInstalled() {
|
|
|
15909
16198
|
return anyPathExists(candidates);
|
|
15910
16199
|
}
|
|
15911
16200
|
async function isAntigravityInstalled() {
|
|
15912
|
-
const home =
|
|
15913
|
-
const candidates = [
|
|
16201
|
+
const home = homedir7();
|
|
16202
|
+
const candidates = [path10.join(home, ".gemini")];
|
|
15914
16203
|
if (process.platform === "darwin") {
|
|
15915
16204
|
candidates.push("/Applications/Antigravity.app");
|
|
15916
|
-
candidates.push(
|
|
15917
|
-
candidates.push(
|
|
16205
|
+
candidates.push(path10.join(home, "Applications", "Antigravity.app"));
|
|
16206
|
+
candidates.push(path10.join(home, "Library", "Application Support", "Antigravity"));
|
|
15918
16207
|
} else if (process.platform === "win32") {
|
|
15919
16208
|
const localApp = process.env.LOCALAPPDATA;
|
|
15920
16209
|
const programFiles = process.env.ProgramFiles;
|
|
15921
16210
|
const programFilesX86 = process.env["ProgramFiles(x86)"];
|
|
15922
16211
|
if (localApp)
|
|
15923
|
-
candidates.push(
|
|
15924
|
-
if (localApp) candidates.push(
|
|
15925
|
-
if (programFiles) candidates.push(
|
|
16212
|
+
candidates.push(path10.join(localApp, "Programs", "Antigravity", "Antigravity.exe"));
|
|
16213
|
+
if (localApp) candidates.push(path10.join(localApp, "Antigravity", "Antigravity.exe"));
|
|
16214
|
+
if (programFiles) candidates.push(path10.join(programFiles, "Antigravity", "Antigravity.exe"));
|
|
15926
16215
|
if (programFilesX86)
|
|
15927
|
-
candidates.push(
|
|
16216
|
+
candidates.push(path10.join(programFilesX86, "Antigravity", "Antigravity.exe"));
|
|
15928
16217
|
} else {
|
|
15929
16218
|
candidates.push("/usr/bin/antigravity");
|
|
15930
16219
|
candidates.push("/usr/local/bin/antigravity");
|
|
@@ -16103,12 +16392,12 @@ function buildContextStreamOpenCodeRemoteServer() {
|
|
|
16103
16392
|
};
|
|
16104
16393
|
}
|
|
16105
16394
|
function kiloConfigDir() {
|
|
16106
|
-
const home =
|
|
16395
|
+
const home = homedir7();
|
|
16107
16396
|
if (process.platform === "win32") {
|
|
16108
|
-
const appData = process.env.APPDATA ||
|
|
16109
|
-
return
|
|
16397
|
+
const appData = process.env.APPDATA || path10.join(home, "AppData", "Roaming");
|
|
16398
|
+
return path10.join(appData, "kilo");
|
|
16110
16399
|
}
|
|
16111
|
-
return
|
|
16400
|
+
return path10.join(home, ".config", "kilo");
|
|
16112
16401
|
}
|
|
16113
16402
|
function buildContextStreamKiloServer(params) {
|
|
16114
16403
|
const env = buildSetupEnv(params);
|
|
@@ -16121,11 +16410,11 @@ function buildContextStreamKiloServer(params) {
|
|
|
16121
16410
|
};
|
|
16122
16411
|
}
|
|
16123
16412
|
async function upsertKiloMcpConfig(filePath, server) {
|
|
16124
|
-
await
|
|
16413
|
+
await fs8.mkdir(path10.dirname(filePath), { recursive: true });
|
|
16125
16414
|
const exists = await fileExists(filePath);
|
|
16126
16415
|
let root = {};
|
|
16127
16416
|
if (exists) {
|
|
16128
|
-
const raw = await
|
|
16417
|
+
const raw = await fs8.readFile(filePath, "utf8").catch(() => "");
|
|
16129
16418
|
const parsed = tryParseJsonLike(raw);
|
|
16130
16419
|
if (!parsed.ok) throw new Error(`Invalid JSON in ${filePath}: ${parsed.error}`);
|
|
16131
16420
|
root = parsed.value;
|
|
@@ -16138,7 +16427,7 @@ async function upsertKiloMcpConfig(filePath, server) {
|
|
|
16138
16427
|
const before = JSON.stringify(root.mcp.contextstream ?? null);
|
|
16139
16428
|
root.mcp.contextstream = server;
|
|
16140
16429
|
const after = JSON.stringify(root.mcp.contextstream ?? null);
|
|
16141
|
-
await
|
|
16430
|
+
await fs8.writeFile(filePath, JSON.stringify(root, null, 2) + "\n", "utf8");
|
|
16142
16431
|
if (!exists) return "created";
|
|
16143
16432
|
return before === after ? "skipped" : "updated";
|
|
16144
16433
|
}
|
|
@@ -16161,11 +16450,11 @@ function tryParseJsonLike(raw) {
|
|
|
16161
16450
|
}
|
|
16162
16451
|
}
|
|
16163
16452
|
async function upsertJsonMcpConfig(filePath, server) {
|
|
16164
|
-
await
|
|
16453
|
+
await fs8.mkdir(path10.dirname(filePath), { recursive: true });
|
|
16165
16454
|
const exists = await fileExists(filePath);
|
|
16166
16455
|
let root = {};
|
|
16167
16456
|
if (exists) {
|
|
16168
|
-
const raw = await
|
|
16457
|
+
const raw = await fs8.readFile(filePath, "utf8").catch(() => "");
|
|
16169
16458
|
const parsed = tryParseJsonLike(raw);
|
|
16170
16459
|
if (!parsed.ok) throw new Error(`Invalid JSON in ${filePath}: ${parsed.error}`);
|
|
16171
16460
|
root = parsed.value;
|
|
@@ -16176,16 +16465,16 @@ async function upsertJsonMcpConfig(filePath, server) {
|
|
|
16176
16465
|
const before = JSON.stringify(root.mcpServers.contextstream ?? null);
|
|
16177
16466
|
root.mcpServers.contextstream = server;
|
|
16178
16467
|
const after = JSON.stringify(root.mcpServers.contextstream ?? null);
|
|
16179
|
-
await
|
|
16468
|
+
await fs8.writeFile(filePath, JSON.stringify(root, null, 2) + "\n", "utf8");
|
|
16180
16469
|
if (!exists) return "created";
|
|
16181
16470
|
return before === after ? "skipped" : "updated";
|
|
16182
16471
|
}
|
|
16183
16472
|
async function upsertJsonVsCodeMcpConfig(filePath, server) {
|
|
16184
|
-
await
|
|
16473
|
+
await fs8.mkdir(path10.dirname(filePath), { recursive: true });
|
|
16185
16474
|
const exists = await fileExists(filePath);
|
|
16186
16475
|
let root = {};
|
|
16187
16476
|
if (exists) {
|
|
16188
|
-
const raw = await
|
|
16477
|
+
const raw = await fs8.readFile(filePath, "utf8").catch(() => "");
|
|
16189
16478
|
const parsed = tryParseJsonLike(raw);
|
|
16190
16479
|
if (!parsed.ok) throw new Error(`Invalid JSON in ${filePath}: ${parsed.error}`);
|
|
16191
16480
|
root = parsed.value;
|
|
@@ -16207,16 +16496,16 @@ async function upsertJsonVsCodeMcpConfig(filePath, server) {
|
|
|
16207
16496
|
root.servers.contextstream = server;
|
|
16208
16497
|
}
|
|
16209
16498
|
const after = JSON.stringify(root.servers.contextstream ?? null);
|
|
16210
|
-
await
|
|
16499
|
+
await fs8.writeFile(filePath, JSON.stringify(root, null, 2) + "\n", "utf8");
|
|
16211
16500
|
if (!exists) return "created";
|
|
16212
16501
|
return before === after ? "skipped" : "updated";
|
|
16213
16502
|
}
|
|
16214
16503
|
async function upsertOpenCodeMcpConfig(filePath, server) {
|
|
16215
|
-
await
|
|
16504
|
+
await fs8.mkdir(path10.dirname(filePath), { recursive: true });
|
|
16216
16505
|
const exists = await fileExists(filePath);
|
|
16217
16506
|
let root = {};
|
|
16218
16507
|
if (exists) {
|
|
16219
|
-
const raw = await
|
|
16508
|
+
const raw = await fs8.readFile(filePath, "utf8").catch(() => "");
|
|
16220
16509
|
const parsed = tryParseJsonLike(raw);
|
|
16221
16510
|
if (!parsed.ok) throw new Error(`Invalid JSON in ${filePath}: ${parsed.error}`);
|
|
16222
16511
|
root = parsed.value;
|
|
@@ -16233,14 +16522,14 @@ async function upsertOpenCodeMcpConfig(filePath, server) {
|
|
|
16233
16522
|
schema: root.$schema ?? null,
|
|
16234
16523
|
contextstream: root.mcp.contextstream ?? null
|
|
16235
16524
|
});
|
|
16236
|
-
await
|
|
16525
|
+
await fs8.writeFile(filePath, JSON.stringify(root, null, 2) + "\n", "utf8");
|
|
16237
16526
|
if (!exists) return "created";
|
|
16238
16527
|
return before === after ? "skipped" : "updated";
|
|
16239
16528
|
}
|
|
16240
16529
|
function claudeDesktopConfigPath() {
|
|
16241
|
-
const home =
|
|
16530
|
+
const home = homedir7();
|
|
16242
16531
|
if (process.platform === "darwin") {
|
|
16243
|
-
return
|
|
16532
|
+
return path10.join(
|
|
16244
16533
|
home,
|
|
16245
16534
|
"Library",
|
|
16246
16535
|
"Application Support",
|
|
@@ -16249,18 +16538,18 @@ function claudeDesktopConfigPath() {
|
|
|
16249
16538
|
);
|
|
16250
16539
|
}
|
|
16251
16540
|
if (process.platform === "win32") {
|
|
16252
|
-
const appData = process.env.APPDATA ||
|
|
16253
|
-
return
|
|
16541
|
+
const appData = process.env.APPDATA || path10.join(home, "AppData", "Roaming");
|
|
16542
|
+
return path10.join(appData, "Claude", "claude_desktop_config.json");
|
|
16254
16543
|
}
|
|
16255
16544
|
if (process.platform === "linux") {
|
|
16256
|
-
return
|
|
16545
|
+
return path10.join(home, ".config", "Claude", "claude_desktop_config.json");
|
|
16257
16546
|
}
|
|
16258
16547
|
return null;
|
|
16259
16548
|
}
|
|
16260
16549
|
async function upsertCodexTomlConfig(filePath, params) {
|
|
16261
|
-
await
|
|
16550
|
+
await fs8.mkdir(path10.dirname(filePath), { recursive: true });
|
|
16262
16551
|
const exists = await fileExists(filePath);
|
|
16263
|
-
const existing = exists ? await
|
|
16552
|
+
const existing = exists ? await fs8.readFile(filePath, "utf8").catch(() => "") : "";
|
|
16264
16553
|
const env = buildSetupEnv(params);
|
|
16265
16554
|
const envLines = formatTomlEnvLines(env);
|
|
16266
16555
|
const marker = "[mcp_servers.contextstream]";
|
|
@@ -16278,15 +16567,15 @@ args = ["--prefer-online", "-y", "@contextstream/mcp-server@latest"]
|
|
|
16278
16567
|
[mcp_servers.contextstream.env]
|
|
16279
16568
|
` + envLines + "\n";
|
|
16280
16569
|
if (!exists) {
|
|
16281
|
-
await
|
|
16570
|
+
await fs8.writeFile(filePath, block.trimStart(), "utf8");
|
|
16282
16571
|
return "created";
|
|
16283
16572
|
}
|
|
16284
16573
|
if (!existing.includes(marker)) {
|
|
16285
|
-
await
|
|
16574
|
+
await fs8.writeFile(filePath, existing.trimEnd() + block, "utf8");
|
|
16286
16575
|
return "updated";
|
|
16287
16576
|
}
|
|
16288
16577
|
if (!existing.includes(envMarker)) {
|
|
16289
|
-
await
|
|
16578
|
+
await fs8.writeFile(
|
|
16290
16579
|
filePath,
|
|
16291
16580
|
existing.trimEnd() + "\n\n" + envMarker + "\n" + envLines + "\n",
|
|
16292
16581
|
"utf8"
|
|
@@ -16336,18 +16625,18 @@ args = ["--prefer-online", "-y", "@contextstream/mcp-server@latest"]
|
|
|
16336
16625
|
}
|
|
16337
16626
|
const updated = out.join("\n");
|
|
16338
16627
|
if (updated === existing) return "skipped";
|
|
16339
|
-
await
|
|
16628
|
+
await fs8.writeFile(filePath, updated, "utf8");
|
|
16340
16629
|
return "updated";
|
|
16341
16630
|
}
|
|
16342
16631
|
async function discoverProjectsUnderFolder(parentFolder) {
|
|
16343
|
-
const entries = await
|
|
16344
|
-
const candidates = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) =>
|
|
16632
|
+
const entries = await fs8.readdir(parentFolder, { withFileTypes: true });
|
|
16633
|
+
const candidates = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => path10.join(parentFolder, e.name));
|
|
16345
16634
|
const projects = [];
|
|
16346
16635
|
for (const dir of candidates) {
|
|
16347
|
-
const hasGit = await fileExists(
|
|
16348
|
-
const hasPkg = await fileExists(
|
|
16349
|
-
const hasCargo = await fileExists(
|
|
16350
|
-
const hasPyProject = await fileExists(
|
|
16636
|
+
const hasGit = await fileExists(path10.join(dir, ".git"));
|
|
16637
|
+
const hasPkg = await fileExists(path10.join(dir, "package.json"));
|
|
16638
|
+
const hasCargo = await fileExists(path10.join(dir, "Cargo.toml"));
|
|
16639
|
+
const hasPyProject = await fileExists(path10.join(dir, "pyproject.toml"));
|
|
16351
16640
|
if (hasGit || hasPkg || hasCargo || hasPyProject) projects.push(dir);
|
|
16352
16641
|
}
|
|
16353
16642
|
return projects;
|
|
@@ -16381,7 +16670,7 @@ async function selectProjectForCurrentDirectory(client, cwd, workspaceId, dryRun
|
|
|
16381
16670
|
if (!workspaceId || workspaceId === "dry-run") {
|
|
16382
16671
|
return void 0;
|
|
16383
16672
|
}
|
|
16384
|
-
const folderName =
|
|
16673
|
+
const folderName = path10.basename(cwd) || "project";
|
|
16385
16674
|
let projects = [];
|
|
16386
16675
|
try {
|
|
16387
16676
|
const result = await client.listProjects({
|
|
@@ -16457,15 +16746,34 @@ async function selectProjectForCurrentDirectory(client, cwd, workspaceId, dryRun
|
|
|
16457
16746
|
return void 0;
|
|
16458
16747
|
}
|
|
16459
16748
|
async function runIndexCommand(targetPath) {
|
|
16460
|
-
const resolvedPath =
|
|
16749
|
+
const resolvedPath = path10.resolve(targetPath);
|
|
16750
|
+
if (isInvariantIgnoredPath(resolvedPath)) {
|
|
16751
|
+
throw new Error(invariantProjectPathMessage(resolvedPath));
|
|
16752
|
+
}
|
|
16753
|
+
if (isBroadProjectPath(resolvedPath) && !broadIngestAllowed()) {
|
|
16754
|
+
throw new Error(broadProjectPathMessage(resolvedPath));
|
|
16755
|
+
}
|
|
16461
16756
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
16462
16757
|
const config = loadConfig2();
|
|
16463
16758
|
const client = new ContextStreamClient(config);
|
|
16464
16759
|
const localConfig = readLocalConfig(resolvedPath);
|
|
16465
16760
|
await indexProjectWithProgress(client, resolvedPath, localConfig?.workspace_id);
|
|
16466
16761
|
}
|
|
16762
|
+
function isBroadProjectPath(candidatePath, homePath = homedir7()) {
|
|
16763
|
+
const resolved = path10.resolve(candidatePath);
|
|
16764
|
+
return resolved === path10.parse(resolved).root || resolved === path10.resolve(homePath);
|
|
16765
|
+
}
|
|
16766
|
+
function broadIngestAllowed() {
|
|
16767
|
+
return process.env.CONTEXTSTREAM_ALLOW_BROAD_INGEST === "1";
|
|
16768
|
+
}
|
|
16769
|
+
function broadProjectPathMessage(candidatePath) {
|
|
16770
|
+
return `Refusing to use ${candidatePath} as a project root because it is the filesystem root or home directory. Run from a repository folder or pass a narrower project path. Set CONTEXTSTREAM_ALLOW_BROAD_INGEST=1 only for an intentional broad ingest.`;
|
|
16771
|
+
}
|
|
16772
|
+
function invariantProjectPathMessage(candidatePath) {
|
|
16773
|
+
return `Refusing to use ${candidatePath} as a project root because it is inside agent state or a credential store. Choose the actual repository checkout instead.`;
|
|
16774
|
+
}
|
|
16467
16775
|
async function indexProjectWithProgress(client, projectPath, workspaceId) {
|
|
16468
|
-
const projectName =
|
|
16776
|
+
const projectName = path10.basename(projectPath);
|
|
16469
16777
|
console.log(`
|
|
16470
16778
|
${colors.bright}Updating index for '${projectName}'...${colors.reset}`);
|
|
16471
16779
|
console.log(`${colors.dim}${projectPath}${colors.reset}
|
|
@@ -16571,6 +16879,7 @@ ${colors.bright}Updating index for '${projectName}'...${colors.reset}`);
|
|
|
16571
16879
|
return 0;
|
|
16572
16880
|
};
|
|
16573
16881
|
let failedBatches = 0;
|
|
16882
|
+
let failedFiles = 0;
|
|
16574
16883
|
try {
|
|
16575
16884
|
for await (const batch of readAllFilesInBatches(projectPath, {
|
|
16576
16885
|
batchSize: 20,
|
|
@@ -16584,7 +16893,7 @@ ${colors.bright}Updating index for '${projectName}'...${colors.reset}`);
|
|
|
16584
16893
|
batchCount++;
|
|
16585
16894
|
} catch {
|
|
16586
16895
|
failedBatches++;
|
|
16587
|
-
|
|
16896
|
+
failedFiles += batch.length;
|
|
16588
16897
|
}
|
|
16589
16898
|
if (filesIndexed > totalFiles) {
|
|
16590
16899
|
totalFiles = filesIndexed + 100;
|
|
@@ -16596,7 +16905,7 @@ ${colors.bright}Updating index for '${projectName}'...${colors.reset}`);
|
|
|
16596
16905
|
process.stdout.write("\r" + " ".repeat(120) + "\r");
|
|
16597
16906
|
if (failedBatches > 0) {
|
|
16598
16907
|
console.log(
|
|
16599
|
-
`${colors.yellow}
|
|
16908
|
+
`${colors.yellow}!${colors.reset} Index update incomplete: ${colors.bright}${filesIndexed.toLocaleString()}${colors.reset} files indexed, ${failedFiles.toLocaleString()} failed (${finalSize}) in ${elapsed}s (${failedBatches} batches had errors; retry the command)`
|
|
16600
16909
|
);
|
|
16601
16910
|
} else {
|
|
16602
16911
|
console.log(
|
|
@@ -16761,10 +17070,10 @@ Code: ${device.user_code}`);
|
|
|
16761
17070
|
if (poll && poll.status === "pending") {
|
|
16762
17071
|
const intervalSeconds = typeof poll.interval === "number" ? poll.interval : 5;
|
|
16763
17072
|
const waitMs = Math.max(1, intervalSeconds) * 1e3;
|
|
16764
|
-
await new Promise((
|
|
17073
|
+
await new Promise((resolve17) => setTimeout(resolve17, waitMs));
|
|
16765
17074
|
continue;
|
|
16766
17075
|
}
|
|
16767
|
-
await new Promise((
|
|
17076
|
+
await new Promise((resolve17) => setTimeout(resolve17, 1e3));
|
|
16768
17077
|
}
|
|
16769
17078
|
if (!accessToken) {
|
|
16770
17079
|
throw new Error(
|
|
@@ -16811,7 +17120,7 @@ Code: ${device.user_code}`);
|
|
|
16811
17120
|
}
|
|
16812
17121
|
}
|
|
16813
17122
|
async function startBackgroundIndexing(projectPath) {
|
|
16814
|
-
const projectLabel =
|
|
17123
|
+
const projectLabel = path10.basename(projectPath);
|
|
16815
17124
|
try {
|
|
16816
17125
|
const { spawn: spawn2 } = await import("node:child_process");
|
|
16817
17126
|
const entry = process.argv[1];
|
|
@@ -17079,7 +17388,7 @@ Editors (from --editors): ${selectedEditors.join(", ")}`);
|
|
|
17079
17388
|
if (mcpScope === "project" && editor !== "codex") continue;
|
|
17080
17389
|
try {
|
|
17081
17390
|
if (editor === "codex") {
|
|
17082
|
-
const filePath =
|
|
17391
|
+
const filePath = path10.join(homedir7(), ".codex", "config.toml");
|
|
17083
17392
|
if (dryRun) {
|
|
17084
17393
|
writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
|
|
17085
17394
|
console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
|
|
@@ -17095,7 +17404,7 @@ Editors (from --editors): ${selectedEditors.join(", ")}`);
|
|
|
17095
17404
|
continue;
|
|
17096
17405
|
}
|
|
17097
17406
|
if (editor === "copilot") {
|
|
17098
|
-
const filePath =
|
|
17407
|
+
const filePath = path10.join(homedir7(), ".copilot", "mcp-config.json");
|
|
17099
17408
|
if (dryRun) {
|
|
17100
17409
|
writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
|
|
17101
17410
|
console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
|
|
@@ -17150,7 +17459,7 @@ Editors (from --editors): ${selectedEditors.join(", ")}`);
|
|
|
17150
17459
|
continue;
|
|
17151
17460
|
}
|
|
17152
17461
|
if (editor === "cursor") {
|
|
17153
|
-
const filePath =
|
|
17462
|
+
const filePath = path10.join(homedir7(), ".cursor", "mcp.json");
|
|
17154
17463
|
if (dryRun) {
|
|
17155
17464
|
writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
|
|
17156
17465
|
console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
|
|
@@ -17162,7 +17471,7 @@ Editors (from --editors): ${selectedEditors.join(", ")}`);
|
|
|
17162
17471
|
continue;
|
|
17163
17472
|
}
|
|
17164
17473
|
if (editor === "windsurf") {
|
|
17165
|
-
const filePath =
|
|
17474
|
+
const filePath = path10.join(homedir7(), ".codeium", "windsurf", "mcp_config.json");
|
|
17166
17475
|
if (dryRun) {
|
|
17167
17476
|
writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
|
|
17168
17477
|
console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
|
|
@@ -17180,7 +17489,7 @@ Editors (from --editors): ${selectedEditors.join(", ")}`);
|
|
|
17180
17489
|
continue;
|
|
17181
17490
|
}
|
|
17182
17491
|
if (editor === "kilo") {
|
|
17183
|
-
const filePath =
|
|
17492
|
+
const filePath = path10.join(kiloConfigDir(), "kilo.jsonc");
|
|
17184
17493
|
if (dryRun) {
|
|
17185
17494
|
writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
|
|
17186
17495
|
console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
|
|
@@ -17246,7 +17555,7 @@ Editors (from --editors): ${selectedEditors.join(", ")}`);
|
|
|
17246
17555
|
});
|
|
17247
17556
|
for (const script of result.installed) {
|
|
17248
17557
|
writeActions.push({ kind: "hooks", target: script, status: "created" });
|
|
17249
|
-
console.log(`- ${EDITOR_LABELS[editor]}: installed ${
|
|
17558
|
+
console.log(`- ${EDITOR_LABELS[editor]}: installed ${path10.basename(script)}`);
|
|
17250
17559
|
}
|
|
17251
17560
|
if (editor === "cursor") {
|
|
17252
17561
|
console.log(
|
|
@@ -17318,11 +17627,18 @@ Editors (from --editors): ${selectedEditors.join(", ")}`);
|
|
|
17318
17627
|
const needsProjects = scope === "project" || scope === "both" || (mcpScope === "project" || mcpScope === "both") && hasProjectMcpEditors || enforceCopilotCanonicalPair;
|
|
17319
17628
|
if (needsProjects) {
|
|
17320
17629
|
console.log("\nProject setup...");
|
|
17321
|
-
const
|
|
17322
|
-
|
|
17323
|
-
|
|
17324
|
-
if (
|
|
17325
|
-
|
|
17630
|
+
const currentFolder = path10.resolve(process.cwd());
|
|
17631
|
+
if (isInvariantIgnoredPath(currentFolder)) {
|
|
17632
|
+
console.log(`- ${invariantProjectPathMessage(currentFolder)}`);
|
|
17633
|
+
} else if (isBroadProjectPath(currentFolder) && !broadIngestAllowed()) {
|
|
17634
|
+
console.log(`- ${broadProjectPathMessage(currentFolder)}`);
|
|
17635
|
+
} else {
|
|
17636
|
+
const addCwd = normalizeInput(
|
|
17637
|
+
await question(`Add current folder as a project? [Y/n] (${currentFolder}): `)
|
|
17638
|
+
);
|
|
17639
|
+
if (addCwd.toLowerCase() !== "n" && addCwd.toLowerCase() !== "no") {
|
|
17640
|
+
projectPaths.add(currentFolder);
|
|
17641
|
+
}
|
|
17326
17642
|
}
|
|
17327
17643
|
while (true) {
|
|
17328
17644
|
console.log("\n 1) Add another project path");
|
|
@@ -17332,13 +17648,22 @@ Editors (from --editors): ${selectedEditors.join(", ")}`);
|
|
|
17332
17648
|
if (choice === "3") break;
|
|
17333
17649
|
if (choice === "1") {
|
|
17334
17650
|
const p = normalizeInput(await question("Project folder path: "));
|
|
17335
|
-
if (p)
|
|
17651
|
+
if (p) {
|
|
17652
|
+
const resolved = path10.resolve(p);
|
|
17653
|
+
if (isInvariantIgnoredPath(resolved)) {
|
|
17654
|
+
console.log(`- ${invariantProjectPathMessage(resolved)}`);
|
|
17655
|
+
} else if (isBroadProjectPath(resolved) && !broadIngestAllowed()) {
|
|
17656
|
+
console.log(`- ${broadProjectPathMessage(resolved)}`);
|
|
17657
|
+
} else {
|
|
17658
|
+
projectPaths.add(resolved);
|
|
17659
|
+
}
|
|
17660
|
+
}
|
|
17336
17661
|
continue;
|
|
17337
17662
|
}
|
|
17338
17663
|
if (choice === "2") {
|
|
17339
17664
|
const parent = normalizeInput(await question("Parent folder path: "));
|
|
17340
17665
|
if (!parent) continue;
|
|
17341
|
-
const parentAbs =
|
|
17666
|
+
const parentAbs = path10.resolve(parent);
|
|
17342
17667
|
const projects2 = await discoverProjectsUnderFolder(parentAbs);
|
|
17343
17668
|
if (projects2.length === 0) {
|
|
17344
17669
|
console.log(
|
|
@@ -17371,7 +17696,7 @@ Applying to ${projects.length} project(s)...`);
|
|
|
17371
17696
|
workspace_id: workspaceId,
|
|
17372
17697
|
workspace_name: workspaceName,
|
|
17373
17698
|
create_parent_mapping: createParentMapping,
|
|
17374
|
-
...
|
|
17699
|
+
...path10.resolve(projectPath) === path10.resolve(process.cwd()) && selectedCurrentProject ? {
|
|
17375
17700
|
project_id: selectedCurrentProject.id,
|
|
17376
17701
|
project_name: selectedCurrentProject.name
|
|
17377
17702
|
} : {},
|
|
@@ -17383,7 +17708,7 @@ Applying to ${projects.length} project(s)...`);
|
|
|
17383
17708
|
});
|
|
17384
17709
|
writeActions.push({
|
|
17385
17710
|
kind: "workspace-config",
|
|
17386
|
-
target:
|
|
17711
|
+
target: path10.join(projectPath, ".contextstream", "config.json"),
|
|
17387
17712
|
status: "created"
|
|
17388
17713
|
});
|
|
17389
17714
|
console.log(`- Linked workspace in ${projectPath}`);
|
|
@@ -17394,11 +17719,11 @@ Applying to ${projects.length} project(s)...`);
|
|
|
17394
17719
|
} else if (workspaceId && workspaceId !== "dry-run" && workspaceName && dryRun) {
|
|
17395
17720
|
writeActions.push({
|
|
17396
17721
|
kind: "workspace-config",
|
|
17397
|
-
target:
|
|
17722
|
+
target: path10.join(projectPath, ".contextstream", "config.json"),
|
|
17398
17723
|
status: "dry-run"
|
|
17399
17724
|
});
|
|
17400
17725
|
}
|
|
17401
|
-
const isCurrentProject =
|
|
17726
|
+
const isCurrentProject = path10.resolve(projectPath) === path10.resolve(process.cwd());
|
|
17402
17727
|
const projectIdForPath = isCurrentProject ? selectedCurrentProject?.id : void 0;
|
|
17403
17728
|
const projectSetupEnv = {
|
|
17404
17729
|
apiUrl,
|
|
@@ -17417,8 +17742,8 @@ Applying to ${projects.length} project(s)...`);
|
|
|
17417
17742
|
if (!shouldWriteProjectMcp) continue;
|
|
17418
17743
|
try {
|
|
17419
17744
|
if (editor === "cursor") {
|
|
17420
|
-
const cursorPath =
|
|
17421
|
-
const vscodePath =
|
|
17745
|
+
const cursorPath = path10.join(projectPath, ".cursor", "mcp.json");
|
|
17746
|
+
const vscodePath = path10.join(projectPath, ".vscode", "mcp.json");
|
|
17422
17747
|
if (dryRun) {
|
|
17423
17748
|
writeActions.push({ kind: "mcp-config", target: cursorPath, status: "dry-run" });
|
|
17424
17749
|
writeActions.push({ kind: "mcp-config", target: vscodePath, status: "dry-run" });
|
|
@@ -17432,12 +17757,12 @@ Applying to ${projects.length} project(s)...`);
|
|
|
17432
17757
|
}
|
|
17433
17758
|
if (editor === "windsurf") {
|
|
17434
17759
|
console.log(
|
|
17435
|
-
`- ${EDITOR_LABELS[editor]}: uses global MCP config only (${
|
|
17760
|
+
`- ${EDITOR_LABELS[editor]}: uses global MCP config only (${path10.join(homedir7(), ".codeium", "windsurf", "mcp_config.json")}).`
|
|
17436
17761
|
);
|
|
17437
17762
|
continue;
|
|
17438
17763
|
}
|
|
17439
17764
|
if (editor === "claude") {
|
|
17440
|
-
const mcpPath =
|
|
17765
|
+
const mcpPath = path10.join(projectPath, ".mcp.json");
|
|
17441
17766
|
if (dryRun) {
|
|
17442
17767
|
writeActions.push({ kind: "mcp-config", target: mcpPath, status: "dry-run" });
|
|
17443
17768
|
} else {
|
|
@@ -17447,7 +17772,7 @@ Applying to ${projects.length} project(s)...`);
|
|
|
17447
17772
|
continue;
|
|
17448
17773
|
}
|
|
17449
17774
|
if (editor === "opencode") {
|
|
17450
|
-
const openCodePath =
|
|
17775
|
+
const openCodePath = path10.join(projectPath, "opencode.json");
|
|
17451
17776
|
if (dryRun) {
|
|
17452
17777
|
writeActions.push({
|
|
17453
17778
|
kind: "mcp-config",
|
|
@@ -17463,7 +17788,7 @@ Applying to ${projects.length} project(s)...`);
|
|
|
17463
17788
|
continue;
|
|
17464
17789
|
}
|
|
17465
17790
|
if (editor === "copilot") {
|
|
17466
|
-
const vsCodePath =
|
|
17791
|
+
const vsCodePath = path10.join(projectPath, ".vscode", "mcp.json");
|
|
17467
17792
|
if (dryRun) {
|
|
17468
17793
|
writeActions.push({ kind: "mcp-config", target: vsCodePath, status: "dry-run" });
|
|
17469
17794
|
} else {
|
|
@@ -17473,7 +17798,7 @@ Applying to ${projects.length} project(s)...`);
|
|
|
17473
17798
|
continue;
|
|
17474
17799
|
}
|
|
17475
17800
|
if (editor === "kilo") {
|
|
17476
|
-
const kiloPath =
|
|
17801
|
+
const kiloPath = path10.join(projectPath, "kilo.jsonc");
|
|
17477
17802
|
if (dryRun) {
|
|
17478
17803
|
writeActions.push({ kind: "mcp-config", target: kiloPath, status: "dry-run" });
|
|
17479
17804
|
} else {
|
|
@@ -17484,7 +17809,7 @@ Applying to ${projects.length} project(s)...`);
|
|
|
17484
17809
|
continue;
|
|
17485
17810
|
}
|
|
17486
17811
|
if (editor === "roo") {
|
|
17487
|
-
const rooPath =
|
|
17812
|
+
const rooPath = path10.join(projectPath, ".roo", "mcp.json");
|
|
17488
17813
|
if (dryRun) {
|
|
17489
17814
|
writeActions.push({ kind: "mcp-config", target: rooPath, status: "dry-run" });
|
|
17490
17815
|
} else {
|
|
@@ -17507,11 +17832,11 @@ Applying to ${projects.length} project(s)...`);
|
|
|
17507
17832
|
const rule = generateRuleContent(editor, {
|
|
17508
17833
|
workspaceName,
|
|
17509
17834
|
workspaceId: workspaceId && workspaceId !== "dry-run" ? workspaceId : void 0,
|
|
17510
|
-
projectName:
|
|
17835
|
+
projectName: path10.basename(projectPath),
|
|
17511
17836
|
mode: getModeForEditor(editor)
|
|
17512
17837
|
});
|
|
17513
17838
|
if (!rule) continue;
|
|
17514
|
-
const filePath =
|
|
17839
|
+
const filePath = path10.join(projectPath, rule.filename);
|
|
17515
17840
|
if (dryRun) {
|
|
17516
17841
|
writeActions.push({ kind: "rules", target: filePath, status: "dry-run" });
|
|
17517
17842
|
continue;
|
|
@@ -17666,7 +17991,7 @@ Applying to ${projects.length} project(s)...`);
|
|
|
17666
17991
|
rl.close();
|
|
17667
17992
|
}
|
|
17668
17993
|
}
|
|
17669
|
-
var EDITOR_LABELS,
|
|
17994
|
+
var EDITOR_LABELS, CONTEXTSTREAM_START_MARKER, CONTEXTSTREAM_END_MARKER, LEGACY_CONTEXTSTREAM_HINTS, LEGACY_CONTEXTSTREAM_ALLOWED_HEADINGS, CONTEXTSTREAM_PREAMBLE_PATTERNS, IS_WINDOWS, DEFAULT_CONTEXTSTREAM_API_URL, DEFAULT_CONTEXTSTREAM_MCP_URL, DEFAULT_CONTEXTSTREAM_TOOLSET, DEFAULT_CONTEXTSTREAM_OUTPUT_FORMAT, DEFAULT_CONTEXTSTREAM_SEARCH_LIMIT, DEFAULT_CONTEXTSTREAM_SEARCH_MAX_CHARS, DEFAULT_CONTEXTSTREAM_TRANSCRIPTS_ENABLED, DEFAULT_CONTEXTSTREAM_HOOK_TRANSCRIPTS_ENABLED, VSCODE_MCP_MODE_ENV, OPENCODE_CONFIG_SCHEMA_URL, REMOTE_HEADER_TOOLSET, REMOTE_HEADER_OUTPUT_FORMAT, REMOTE_HEADER_SEARCH_LIMIT, REMOTE_HEADER_SEARCH_MAX_CHARS, REMOTE_HEADER_TRANSCRIPTS_ENABLED, REMOTE_HEADER_CONSOLIDATED, __setupTestUtils, colors, spinnerFrames;
|
|
17670
17995
|
var init_setup = __esm({
|
|
17671
17996
|
"src/setup.ts"() {
|
|
17672
17997
|
"use strict";
|
|
@@ -17677,6 +18002,7 @@ var init_setup = __esm({
|
|
|
17677
18002
|
init_credentials();
|
|
17678
18003
|
init_hooks_config();
|
|
17679
18004
|
init_files();
|
|
18005
|
+
init_ignore();
|
|
17680
18006
|
init_workspace_config();
|
|
17681
18007
|
EDITOR_LABELS = {
|
|
17682
18008
|
codex: "Codex CLI",
|
|
@@ -17691,9 +18017,9 @@ var init_setup = __esm({
|
|
|
17691
18017
|
aider: "Aider",
|
|
17692
18018
|
antigravity: "Antigravity (Google)"
|
|
17693
18019
|
};
|
|
17694
|
-
|
|
17695
|
-
|
|
17696
|
-
|
|
18020
|
+
CONTEXTSTREAM_START_MARKER = "<!-- BEGIN ContextStream -->";
|
|
18021
|
+
CONTEXTSTREAM_END_MARKER = "<!-- END ContextStream -->";
|
|
18022
|
+
LEGACY_CONTEXTSTREAM_HINTS = [
|
|
17697
18023
|
"contextstream integration",
|
|
17698
18024
|
"contextstream v0.4",
|
|
17699
18025
|
"contextstream v0.3",
|
|
@@ -17702,7 +18028,7 @@ var init_setup = __esm({
|
|
|
17702
18028
|
"contextstream mcp",
|
|
17703
18029
|
"contextstream tools"
|
|
17704
18030
|
];
|
|
17705
|
-
|
|
18031
|
+
LEGACY_CONTEXTSTREAM_ALLOWED_HEADINGS = [
|
|
17706
18032
|
"contextstream",
|
|
17707
18033
|
"tl;dr",
|
|
17708
18034
|
"required every message",
|
|
@@ -17723,7 +18049,7 @@ var init_setup = __esm({
|
|
|
17723
18049
|
"plans & tasks",
|
|
17724
18050
|
"complete action reference"
|
|
17725
18051
|
];
|
|
17726
|
-
|
|
18052
|
+
CONTEXTSTREAM_PREAMBLE_PATTERNS = [
|
|
17727
18053
|
/^#\s+workspace:/i,
|
|
17728
18054
|
/^#\s+project:/i,
|
|
17729
18055
|
/^#\s+workspace id:/i,
|
|
@@ -17785,11 +18111,13 @@ var init_setup = __esm({
|
|
|
17785
18111
|
// src/hooks/post-write.ts
|
|
17786
18112
|
var post_write_exports = {};
|
|
17787
18113
|
__export(post_write_exports, {
|
|
17788
|
-
runPostWriteHook: () => runPostWriteHook
|
|
18114
|
+
runPostWriteHook: () => runPostWriteHook,
|
|
18115
|
+
shouldIgnoreHookPath: () => shouldIgnoreHookPath
|
|
17789
18116
|
});
|
|
17790
18117
|
import * as fs10 from "node:fs";
|
|
17791
18118
|
import * as path12 from "node:path";
|
|
17792
18119
|
import { homedir as homedir9 } from "node:os";
|
|
18120
|
+
import { execFileSync } from "node:child_process";
|
|
17793
18121
|
function extractFilePath(input) {
|
|
17794
18122
|
if (input.tool_input) {
|
|
17795
18123
|
const filePath = input.tool_input.file_path || input.tool_input.notebook_path || input.tool_input.path;
|
|
@@ -17895,6 +18223,29 @@ function shouldIndexFile(filePath) {
|
|
|
17895
18223
|
}
|
|
17896
18224
|
return true;
|
|
17897
18225
|
}
|
|
18226
|
+
function shouldIgnoreHookPath(projectRoot, filePath) {
|
|
18227
|
+
const root = path12.resolve(projectRoot);
|
|
18228
|
+
const absolutePath = path12.resolve(filePath);
|
|
18229
|
+
if (isInvariantIgnoredPath(absolutePath)) {
|
|
18230
|
+
return true;
|
|
18231
|
+
}
|
|
18232
|
+
const relativePath = path12.relative(root, absolutePath).replace(/\\/g, "/");
|
|
18233
|
+
if (!relativePath || relativePath === ".." || relativePath.startsWith("../")) {
|
|
18234
|
+
return true;
|
|
18235
|
+
}
|
|
18236
|
+
if (loadIgnorePatternsSync(root).ignores(relativePath)) {
|
|
18237
|
+
return true;
|
|
18238
|
+
}
|
|
18239
|
+
try {
|
|
18240
|
+
execFileSync("git", ["-C", root, "check-ignore", "-q", "--", relativePath], {
|
|
18241
|
+
stdio: "ignore",
|
|
18242
|
+
timeout: 2e3
|
|
18243
|
+
});
|
|
18244
|
+
return true;
|
|
18245
|
+
} catch {
|
|
18246
|
+
return false;
|
|
18247
|
+
}
|
|
18248
|
+
}
|
|
17898
18249
|
function detectLanguage2(filePath) {
|
|
17899
18250
|
const ext = path12.extname(filePath).toLowerCase();
|
|
17900
18251
|
const langMap = {
|
|
@@ -18040,6 +18391,9 @@ async function runPostWriteHook() {
|
|
|
18040
18391
|
if (!projectRoot) {
|
|
18041
18392
|
process.exit(0);
|
|
18042
18393
|
}
|
|
18394
|
+
if (shouldIgnoreHookPath(projectRoot, absolutePath)) {
|
|
18395
|
+
process.exit(0);
|
|
18396
|
+
}
|
|
18043
18397
|
const localConfig = findLocalConfig(projectRoot);
|
|
18044
18398
|
if (!localConfig?.project_id) {
|
|
18045
18399
|
process.exit(0);
|
|
@@ -18058,6 +18412,7 @@ var API_URL, API_KEY, ENABLED, INDEXABLE_EXTENSIONS, MAX_FILE_SIZE2, isDirectRun
|
|
|
18058
18412
|
var init_post_write = __esm({
|
|
18059
18413
|
"src/hooks/post-write.ts"() {
|
|
18060
18414
|
"use strict";
|
|
18415
|
+
init_ignore();
|
|
18061
18416
|
API_URL = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
|
|
18062
18417
|
API_KEY = process.env.CONTEXTSTREAM_API_KEY || "";
|
|
18063
18418
|
ENABLED = process.env.CONTEXTSTREAM_POSTWRITE_ENABLED !== "false";
|
|
@@ -20020,9 +20375,10 @@ var init_user_prompt_submit = __esm({
|
|
|
20020
20375
|
API_KEY2 = process.env.CONTEXTSTREAM_API_KEY || "";
|
|
20021
20376
|
WORKSPACE_ID = null;
|
|
20022
20377
|
PROJECT_ID = null;
|
|
20023
|
-
REMINDER = `[CONTEXTSTREAM] On the first message in every session call mcp__contextstream__init(...), then call mcp__contextstream__context(user_message="...", save_exchange=true, session_id="<session-id>") FIRST before any other tool. On subsequent messages, default to context first.
|
|
20378
|
+
REMINDER = `[CONTEXTSTREAM] On the first message in every session call mcp__contextstream__init(...), then call mcp__contextstream__context(user_message="...", save_exchange=true, session_id="<session-id>") FIRST before any other tool. On subsequent messages, default to context first. The fresh-context direct-read lane is limited to workspace list/get; memory list_*; help version/tools/auth; project list/get/index_status; and reminder list/active. Recall, decisions, searches, specific reads, writes, and indexing require context first. Fresh [GROUNDING] is the first continuation retrieval; do not immediately duplicate it with recall. Response contains dynamic rules, lessons, preferences.
|
|
20024
20379
|
SEARCH-FIRST: Use mcp__contextstream__search(mode="auto") before Glob/Grep/Read/Explore/Task/EnterPlanMode. In planning, never use EnterPlanMode or Task(Explore) for file-by-file discovery.
|
|
20025
20380
|
COMMON MEMORY CALLS: list docs via memory(action="list_docs"), list lessons via session(action="get_lessons"), list plans via session(action="list_plans"), list tasks/todos via memory(action="list_tasks"|"list_todos").
|
|
20381
|
+
HANDOFFS: A generic handoff must create entity(kind="handoff", action="create", body={title,summary,scope,next_steps}); add capsule only for a requested portable bundle/share link. HANDOFF.md or prose alone is not the canonical handoff.
|
|
20026
20382
|
[END]`;
|
|
20027
20383
|
FULL_REMINDER = `[CONTEXTSTREAM RULES - MANDATORY]
|
|
20028
20384
|
|
|
@@ -20031,7 +20387,8 @@ COMMON MEMORY CALLS: list docs via memory(action="list_docs"), list lessons via
|
|
|
20031
20387
|
- Check response for: [LESSONS_WARNING], [RULES_NOTICE], preferences
|
|
20032
20388
|
- save_exchange=true saves each conversation turn for later retrieval
|
|
20033
20389
|
- Use a consistent session_id for the entire conversation (generate once on first message)
|
|
20034
|
-
- On subsequent messages, default to context() first.
|
|
20390
|
+
- On subsequent messages, default to context() first. Direct-read bypass is limited to workspace list/get; memory list_*; help version/tools/auth; project list/get/index_status; and reminder list/active when context is fresh and no state-changing tool has run.
|
|
20391
|
+
- Fresh [GROUNDING] is the first continuation retrieval. Do not immediately duplicate sufficient grounding with session(action="recall").
|
|
20035
20392
|
|
|
20036
20393
|
2. FOR CODE SEARCH: Check index status, then search appropriately
|
|
20037
20394
|
\u2705 Use mcp__contextstream__search(mode="auto", query="...") BEFORE Glob/Grep/Read/Explore/Task/EnterPlanMode
|
|
@@ -20049,13 +20406,18 @@ COMMON MEMORY CALLS: list docs via memory(action="list_docs"), list lessons via
|
|
|
20049
20406
|
\u274C DO NOT use EnterPlanMode or Task(subagent_type="Explore") for file-by-file discovery
|
|
20050
20407
|
\u2705 For planning discovery: mcp__contextstream__search(mode="auto", query="...", output_format="paths")
|
|
20051
20408
|
|
|
20052
|
-
5.
|
|
20409
|
+
5. FOR HANDOFFS: Create the canonical durable handoff entity
|
|
20410
|
+
\u2705 mcp__contextstream__entity(kind="handoff", action="create", body={title,summary,scope,next_steps})
|
|
20411
|
+
\u2705 Add a capsule only when a portable bundle or share link is requested
|
|
20412
|
+
\u274C HANDOFF.md, a scratch prompt, or prose alone is not the canonical handoff
|
|
20413
|
+
|
|
20414
|
+
6. CHECK THESE from context() response:
|
|
20053
20415
|
- Lessons: Past mistakes to avoid (shown as warnings)
|
|
20054
20416
|
- Reminders: Active reminders for this project
|
|
20055
20417
|
- Preferences: User's coding style and preferences
|
|
20056
20418
|
- Rules: Dynamic rules matched to current task
|
|
20057
20419
|
|
|
20058
|
-
|
|
20420
|
+
7. SKIP CONTEXTSTREAM: If user preference says "skip contextstream", use local tools instead
|
|
20059
20421
|
[END]`;
|
|
20060
20422
|
ENHANCED_REMINDER_HEADER = `\u2B21 ContextStream \u2014 Smart Context & Memory
|
|
20061
20423
|
|
|
@@ -21629,10 +21991,10 @@ init_client();
|
|
|
21629
21991
|
init_files();
|
|
21630
21992
|
init_rules_templates();
|
|
21631
21993
|
init_version();
|
|
21632
|
-
import * as
|
|
21633
|
-
import * as
|
|
21994
|
+
import * as fs9 from "node:fs";
|
|
21995
|
+
import * as path11 from "node:path";
|
|
21634
21996
|
import { execFile as execFile2 } from "node:child_process";
|
|
21635
|
-
import { homedir as
|
|
21997
|
+
import { homedir as homedir8 } from "node:os";
|
|
21636
21998
|
import { promisify as promisify3 } from "node:util";
|
|
21637
21999
|
|
|
21638
22000
|
// src/tool-catalog.ts
|
|
@@ -22455,6 +22817,71 @@ var HotPathStore = class {
|
|
|
22455
22817
|
var globalHotPathStore = new HotPathStore();
|
|
22456
22818
|
|
|
22457
22819
|
// src/tools.ts
|
|
22820
|
+
init_setup();
|
|
22821
|
+
init_ignore();
|
|
22822
|
+
var MCP_RELEASES_URL = "https://github.com/contextstream/mcp-server/releases";
|
|
22823
|
+
var ENTITY_TOOL_DESCRIPTION = "Structured taxonomy entities \u2014 tickets, handoffs, incidents, releases, experiments, goals, key_results, sprints, reviews, risks, and backlog views. Use kind=handoff with action=create when the user asks to create or prepare a handoff, hand work over, or continue in another agent/session; include title, summary, scope, and next_steps. Add to_user_id only when the recipient is known; omit to_user_id rather than inventing a recipient. A handoff entity is the canonical durable handoff. A capsule is an optional additional portable artifact when the user explicitly asks for a capsule, bundle, or share link. Never substitute HANDOFF.md, a scratch prompt, a generic document/event, or prose alone. Use memory(create_task) for lightweight tasks, memory(create_doc) for runbooks/docs, and session(capture) for timeline events.";
|
|
22824
|
+
var CAPSULE_TOOL_DESCRIPTION = "ContextCapsule creates portable, shareable, hydrate-on-demand context artifacts. Use it when the user explicitly asks for a capsule, portable bundle, share link, team link, external-agent link, bootstrap prompt, share-token graph, or capsule audit. For a generic handoff, first create entity(kind=handoff); if a portable bundle or link is also requested, create both the handoff entity and capsule. A capsule is the artifact, not a replacement for the canonical handoff. Do not use capsule for normal turn-by-turn retrieval; use context instead.";
|
|
22825
|
+
function taskUpdateIsStatusOnly(update) {
|
|
22826
|
+
return typeof update.status === "string" && update.status.length > 0 && update.title === void 0 && update.content === void 0 && update.description === void 0 && update.priority === void 0 && update.order === void 0 && update.plan_id === void 0 && update.plan_step_id === void 0 && update.code_refs === void 0 && update.tags === void 0 && update.blocked_reason === void 0;
|
|
22827
|
+
}
|
|
22828
|
+
function extractTaskStatus(result) {
|
|
22829
|
+
if (!result || typeof result !== "object") return void 0;
|
|
22830
|
+
const value = result;
|
|
22831
|
+
const candidates = [
|
|
22832
|
+
value.status,
|
|
22833
|
+
value.task?.status,
|
|
22834
|
+
value.data?.status,
|
|
22835
|
+
value.data?.task?.status
|
|
22836
|
+
];
|
|
22837
|
+
return candidates.find((candidate) => typeof candidate === "string");
|
|
22838
|
+
}
|
|
22839
|
+
function buildMcpVersionInfo(currentVersion, remote) {
|
|
22840
|
+
const wrapped = remote && typeof remote === "object" ? remote : {};
|
|
22841
|
+
const payload = wrapped.data && typeof wrapped.data === "object" ? wrapped.data : wrapped;
|
|
22842
|
+
return {
|
|
22843
|
+
...payload,
|
|
22844
|
+
name: "contextstream-mcp",
|
|
22845
|
+
runtime_type: "legacy-typescript-mcp",
|
|
22846
|
+
version: currentVersion,
|
|
22847
|
+
latest_version: typeof payload.latest_version === "string" ? payload.latest_version : currentVersion,
|
|
22848
|
+
release_url: typeof payload.release_url === "string" ? payload.release_url : MCP_RELEASES_URL
|
|
22849
|
+
};
|
|
22850
|
+
}
|
|
22851
|
+
function dailyRecapItems(result) {
|
|
22852
|
+
if (Array.isArray(result)) return result;
|
|
22853
|
+
if (!result || typeof result !== "object") return [];
|
|
22854
|
+
const value = result;
|
|
22855
|
+
const candidates = [
|
|
22856
|
+
value.recaps,
|
|
22857
|
+
value.items,
|
|
22858
|
+
value.data,
|
|
22859
|
+
value.data?.recaps,
|
|
22860
|
+
value.data?.items
|
|
22861
|
+
];
|
|
22862
|
+
return candidates.find(Array.isArray) || [];
|
|
22863
|
+
}
|
|
22864
|
+
function formatDailyRecaps(result) {
|
|
22865
|
+
const recaps = dailyRecapItems(result);
|
|
22866
|
+
if (recaps.length === 0) {
|
|
22867
|
+
return `No completed Daily Recaps found for this workspace. Automatic recaps run around 23:00 in the user's configured timezone; use session(action="trigger_recap") to queue one now.`;
|
|
22868
|
+
}
|
|
22869
|
+
const lines = recaps.map((recap) => {
|
|
22870
|
+
const date = typeof recap.recap_date === "string" ? recap.recap_date : "unknown date";
|
|
22871
|
+
const generatedAt = typeof recap.generated_at === "string" ? recap.generated_at : "generation timestamp unavailable";
|
|
22872
|
+
const headline = typeof recap.headline === "string" ? recap.headline.trim() : "";
|
|
22873
|
+
return `- ${date} \u2014 generated ${generatedAt}${headline ? ` \u2014 ${headline}` : ""}`;
|
|
22874
|
+
});
|
|
22875
|
+
return [`Daily Recaps (${recaps.length}), newest first:`, ...lines].join("\n");
|
|
22876
|
+
}
|
|
22877
|
+
async function loadMcpVersionInfo(client) {
|
|
22878
|
+
try {
|
|
22879
|
+
return buildMcpVersionInfo(VERSION, await client.getMcpVersion());
|
|
22880
|
+
} catch (error) {
|
|
22881
|
+
logDebug(`MCP release metadata unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
22882
|
+
return buildMcpVersionInfo(VERSION, void 0);
|
|
22883
|
+
}
|
|
22884
|
+
}
|
|
22458
22885
|
function stringOrArray(itemSchema) {
|
|
22459
22886
|
return external_exports.union([
|
|
22460
22887
|
external_exports.array(itemSchema),
|
|
@@ -22954,22 +23381,22 @@ async function resolveIdOrPrefix(client, value, fieldName, workspaceId, projectI
|
|
|
22954
23381
|
}
|
|
22955
23382
|
var RULES_NOTICE_CACHE_TTL_MS = 10 * 60 * 1e3;
|
|
22956
23383
|
var RULES_VERSION_REGEX = /(?:Rules Version:\s*|^v)([0-9][0-9A-Za-z.\-]*)/im;
|
|
22957
|
-
var
|
|
22958
|
-
var
|
|
23384
|
+
var CONTEXTSTREAM_START_MARKER2 = "<!-- BEGIN ContextStream -->";
|
|
23385
|
+
var CONTEXTSTREAM_END_MARKER2 = "<!-- END ContextStream -->";
|
|
22959
23386
|
var RULES_PROJECT_FILES = {
|
|
22960
23387
|
codex: "AGENTS.md",
|
|
22961
23388
|
claude: "CLAUDE.md",
|
|
22962
|
-
copilot:
|
|
23389
|
+
copilot: path11.join(".github", "copilot-instructions.md"),
|
|
22963
23390
|
cursor: ".cursorrules",
|
|
22964
23391
|
cline: ".clinerules",
|
|
22965
|
-
kilo:
|
|
22966
|
-
roo:
|
|
23392
|
+
kilo: path11.join(".kilocode", "rules", "contextstream.md"),
|
|
23393
|
+
roo: path11.join(".roo", "rules", "contextstream.md"),
|
|
22967
23394
|
aider: ".aider.conf.yml"
|
|
22968
23395
|
};
|
|
22969
23396
|
var RULES_GLOBAL_FILES = {
|
|
22970
|
-
codex: [
|
|
22971
|
-
kilo: [
|
|
22972
|
-
roo: [
|
|
23397
|
+
codex: [path11.join(homedir8(), ".codex", "AGENTS.md")],
|
|
23398
|
+
kilo: [path11.join(homedir8(), ".kilocode", "rules", "contextstream.md")],
|
|
23399
|
+
roo: [path11.join(homedir8(), ".roo", "rules", "contextstream.md")]
|
|
22973
23400
|
};
|
|
22974
23401
|
var rulesNoticeCache = /* @__PURE__ */ new Map();
|
|
22975
23402
|
function compareVersions2(v1, v2) {
|
|
@@ -22988,12 +23415,12 @@ function extractRulesVersions(content) {
|
|
|
22988
23415
|
return Array.from(content.matchAll(regex)).map((match) => match[1]?.trim()).filter((version) => Boolean(version));
|
|
22989
23416
|
}
|
|
22990
23417
|
function extractContextStreamMarkerBlock(content) {
|
|
22991
|
-
const startIdx = content.indexOf(
|
|
22992
|
-
const endIdx = content.indexOf(
|
|
23418
|
+
const startIdx = content.indexOf(CONTEXTSTREAM_START_MARKER2);
|
|
23419
|
+
const endIdx = content.indexOf(CONTEXTSTREAM_END_MARKER2);
|
|
22993
23420
|
if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
|
|
22994
23421
|
return null;
|
|
22995
23422
|
}
|
|
22996
|
-
return content.slice(startIdx +
|
|
23423
|
+
return content.slice(startIdx + CONTEXTSTREAM_START_MARKER2.length, endIdx).trim();
|
|
22997
23424
|
}
|
|
22998
23425
|
function extractRulesVersion(content) {
|
|
22999
23426
|
const markerBlock = extractContextStreamMarkerBlock(content);
|
|
@@ -23021,7 +23448,7 @@ function resolveRulesCandidatePaths(folderPath, editorKey) {
|
|
|
23021
23448
|
if (!folderPath) return;
|
|
23022
23449
|
const rel = RULES_PROJECT_FILES[key];
|
|
23023
23450
|
if (rel) {
|
|
23024
|
-
candidates.add(
|
|
23451
|
+
candidates.add(path11.join(folderPath, rel));
|
|
23025
23452
|
}
|
|
23026
23453
|
};
|
|
23027
23454
|
const addGlobal = (key) => {
|
|
@@ -23053,7 +23480,7 @@ function resolveFolderPath(inputPath, sessionManager) {
|
|
|
23053
23480
|
const indicators = [".git", "package.json", "Cargo.toml", "pyproject.toml", ".contextstream"];
|
|
23054
23481
|
const hasIndicator = indicators.some((entry) => {
|
|
23055
23482
|
try {
|
|
23056
|
-
return
|
|
23483
|
+
return fs9.existsSync(path11.join(cwd, entry));
|
|
23057
23484
|
} catch {
|
|
23058
23485
|
return false;
|
|
23059
23486
|
}
|
|
@@ -23072,7 +23499,7 @@ function getRulesNotice(folderPath, clientName) {
|
|
|
23072
23499
|
return cached.notice;
|
|
23073
23500
|
}
|
|
23074
23501
|
const candidates = resolveRulesCandidatePaths(folderPath, editorKey);
|
|
23075
|
-
const existing = candidates.filter((filePath) =>
|
|
23502
|
+
const existing = candidates.filter((filePath) => fs9.existsSync(filePath));
|
|
23076
23503
|
if (existing.length === 0) {
|
|
23077
23504
|
const updateCommand2 = "generate_rules()";
|
|
23078
23505
|
const notice2 = {
|
|
@@ -23094,7 +23521,7 @@ function getRulesNotice(folderPath, clientName) {
|
|
|
23094
23521
|
const versions = [];
|
|
23095
23522
|
for (const filePath of existing) {
|
|
23096
23523
|
try {
|
|
23097
|
-
const content =
|
|
23524
|
+
const content = fs9.readFileSync(filePath, "utf-8");
|
|
23098
23525
|
const version = extractRulesVersion(content);
|
|
23099
23526
|
if (!version) {
|
|
23100
23527
|
filesMissingVersion.push(filePath);
|
|
@@ -23131,7 +23558,7 @@ function getRulesNotice(folderPath, clientName) {
|
|
|
23131
23558
|
rulesNoticeCache.set(cacheKey, { checkedAt: Date.now(), notice });
|
|
23132
23559
|
return notice;
|
|
23133
23560
|
}
|
|
23134
|
-
var
|
|
23561
|
+
var LEGACY_CONTEXTSTREAM_HINTS2 = [
|
|
23135
23562
|
"contextstream integration",
|
|
23136
23563
|
"contextstream v0.4",
|
|
23137
23564
|
"contextstream v0.3",
|
|
@@ -23140,7 +23567,7 @@ var LEGACY_CONTEXTSTREAM_HINTS = [
|
|
|
23140
23567
|
"contextstream mcp",
|
|
23141
23568
|
"contextstream tools"
|
|
23142
23569
|
];
|
|
23143
|
-
var
|
|
23570
|
+
var LEGACY_CONTEXTSTREAM_ALLOWED_HEADINGS2 = [
|
|
23144
23571
|
"contextstream",
|
|
23145
23572
|
"tl;dr",
|
|
23146
23573
|
"required every message",
|
|
@@ -23161,7 +23588,7 @@ var LEGACY_CONTEXTSTREAM_ALLOWED_HEADINGS = [
|
|
|
23161
23588
|
"plans & tasks",
|
|
23162
23589
|
"complete action reference"
|
|
23163
23590
|
];
|
|
23164
|
-
var
|
|
23591
|
+
var CONTEXTSTREAM_PREAMBLE_PATTERNS2 = [
|
|
23165
23592
|
/^#\s+workspace:/i,
|
|
23166
23593
|
/^#\s+project:/i,
|
|
23167
23594
|
/^#\s+workspace id:/i,
|
|
@@ -23173,34 +23600,34 @@ var CONTEXTSTREAM_PREAMBLE_PATTERNS = [
|
|
|
23173
23600
|
/^#\s+roo code rules$/i,
|
|
23174
23601
|
/^#\s+aider configuration$/i
|
|
23175
23602
|
];
|
|
23176
|
-
function
|
|
23177
|
-
return `${
|
|
23603
|
+
function wrapWithMarkers2(content) {
|
|
23604
|
+
return `${CONTEXTSTREAM_START_MARKER2}
|
|
23178
23605
|
${content.trim()}
|
|
23179
|
-
${
|
|
23606
|
+
${CONTEXTSTREAM_END_MARKER2}`;
|
|
23180
23607
|
}
|
|
23181
|
-
function
|
|
23608
|
+
function isLegacyContextStreamRules2(content) {
|
|
23182
23609
|
const lower = content.toLowerCase();
|
|
23183
23610
|
if (!lower.includes("contextstream")) return false;
|
|
23184
|
-
if (!
|
|
23611
|
+
if (!LEGACY_CONTEXTSTREAM_HINTS2.some((hint) => lower.includes(hint))) return false;
|
|
23185
23612
|
const headingRegex = /^#{1,6}\s+(.+)$/gm;
|
|
23186
23613
|
let hasHeading = false;
|
|
23187
23614
|
let match;
|
|
23188
23615
|
while ((match = headingRegex.exec(content)) !== null) {
|
|
23189
23616
|
hasHeading = true;
|
|
23190
23617
|
const heading = match[1].trim().toLowerCase();
|
|
23191
|
-
const allowed =
|
|
23618
|
+
const allowed = LEGACY_CONTEXTSTREAM_ALLOWED_HEADINGS2.some(
|
|
23192
23619
|
(prefix) => heading.startsWith(prefix)
|
|
23193
23620
|
);
|
|
23194
23621
|
if (!allowed) return false;
|
|
23195
23622
|
}
|
|
23196
23623
|
return hasHeading;
|
|
23197
23624
|
}
|
|
23198
|
-
function
|
|
23625
|
+
function isContextStreamPreamble2(line) {
|
|
23199
23626
|
const trimmed = line.trim();
|
|
23200
23627
|
if (!trimmed) return false;
|
|
23201
|
-
return
|
|
23628
|
+
return CONTEXTSTREAM_PREAMBLE_PATTERNS2.some((pattern) => pattern.test(trimmed));
|
|
23202
23629
|
}
|
|
23203
|
-
function
|
|
23630
|
+
function findContextStreamHeading2(lines) {
|
|
23204
23631
|
const headingRegex = /^(#{1,6})\s+(.+)$/;
|
|
23205
23632
|
for (let i = 0; i < lines.length; i += 1) {
|
|
23206
23633
|
const match = headingRegex.exec(lines[i]);
|
|
@@ -23211,7 +23638,7 @@ function findContextStreamHeading(lines) {
|
|
|
23211
23638
|
}
|
|
23212
23639
|
return null;
|
|
23213
23640
|
}
|
|
23214
|
-
function
|
|
23641
|
+
function findSectionEnd2(lines, startLine, level) {
|
|
23215
23642
|
const headingRegex = /^(#{1,6})\s+(.+)$/;
|
|
23216
23643
|
for (let i = startLine + 1; i < lines.length; i += 1) {
|
|
23217
23644
|
const match = headingRegex.exec(lines[i]);
|
|
@@ -23220,16 +23647,16 @@ function findSectionEnd(lines, startLine, level) {
|
|
|
23220
23647
|
}
|
|
23221
23648
|
return lines.length;
|
|
23222
23649
|
}
|
|
23223
|
-
function
|
|
23650
|
+
function extractContextStreamBlock2(content) {
|
|
23224
23651
|
const lines = content.split(/\r?\n/);
|
|
23225
|
-
const heading =
|
|
23652
|
+
const heading = findContextStreamHeading2(lines);
|
|
23226
23653
|
if (!heading) return content.trim();
|
|
23227
|
-
const endLine =
|
|
23654
|
+
const endLine = findSectionEnd2(lines, heading.index, heading.level);
|
|
23228
23655
|
return lines.slice(heading.index, endLine).join("\n").trim();
|
|
23229
23656
|
}
|
|
23230
|
-
function
|
|
23657
|
+
function findLegacyContextStreamSection2(content) {
|
|
23231
23658
|
const lines = content.split(/\r?\n/);
|
|
23232
|
-
const heading =
|
|
23659
|
+
const heading = findContextStreamHeading2(lines);
|
|
23233
23660
|
if (!heading) return null;
|
|
23234
23661
|
let startLine = heading.index;
|
|
23235
23662
|
for (let i = heading.index - 1; i >= 0; i -= 1) {
|
|
@@ -23238,39 +23665,39 @@ function findLegacyContextStreamSection(content) {
|
|
|
23238
23665
|
startLine = i;
|
|
23239
23666
|
continue;
|
|
23240
23667
|
}
|
|
23241
|
-
if (
|
|
23668
|
+
if (isContextStreamPreamble2(line)) {
|
|
23242
23669
|
startLine = i;
|
|
23243
23670
|
continue;
|
|
23244
23671
|
}
|
|
23245
23672
|
break;
|
|
23246
23673
|
}
|
|
23247
|
-
const endLine =
|
|
23674
|
+
const endLine = findSectionEnd2(lines, heading.index, heading.level);
|
|
23248
23675
|
return { startLine, endLine, contextLine: heading.index };
|
|
23249
23676
|
}
|
|
23250
|
-
function
|
|
23677
|
+
function blockHasPreamble2(block) {
|
|
23251
23678
|
const lines = block.split(/\r?\n/);
|
|
23252
23679
|
for (const line of lines) {
|
|
23253
23680
|
const trimmed = line.trim();
|
|
23254
23681
|
if (!trimmed) continue;
|
|
23255
|
-
if (
|
|
23682
|
+
if (isContextStreamPreamble2(trimmed)) return true;
|
|
23256
23683
|
if (/^#{1,6}\s+/.test(trimmed)) return false;
|
|
23257
23684
|
}
|
|
23258
23685
|
return false;
|
|
23259
23686
|
}
|
|
23260
|
-
function
|
|
23261
|
-
const fullWrapped =
|
|
23262
|
-
const blockWrapped =
|
|
23263
|
-
const startIdx = existing.indexOf(
|
|
23264
|
-
const endIdx = existing.indexOf(
|
|
23687
|
+
function replaceContextStreamBlock2(existing, content) {
|
|
23688
|
+
const fullWrapped = wrapWithMarkers2(content);
|
|
23689
|
+
const blockWrapped = wrapWithMarkers2(extractContextStreamBlock2(content));
|
|
23690
|
+
const startIdx = existing.indexOf(CONTEXTSTREAM_START_MARKER2);
|
|
23691
|
+
const endIdx = existing.indexOf(CONTEXTSTREAM_END_MARKER2);
|
|
23265
23692
|
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
23266
|
-
const existingBlock = existing.slice(startIdx +
|
|
23267
|
-
const replacement =
|
|
23693
|
+
const existingBlock = existing.slice(startIdx + CONTEXTSTREAM_START_MARKER2.length, endIdx);
|
|
23694
|
+
const replacement = blockHasPreamble2(existingBlock) ? fullWrapped : blockWrapped;
|
|
23268
23695
|
const before = existing.substring(0, startIdx).trimEnd();
|
|
23269
|
-
const after = existing.substring(endIdx +
|
|
23696
|
+
const after = existing.substring(endIdx + CONTEXTSTREAM_END_MARKER2.length).trimStart();
|
|
23270
23697
|
const merged = [before, replacement, after].filter((part) => part.length > 0).join("\n\n");
|
|
23271
23698
|
return { content: merged.trim() + "\n", status: "updated" };
|
|
23272
23699
|
}
|
|
23273
|
-
const legacy =
|
|
23700
|
+
const legacy = findLegacyContextStreamSection2(existing);
|
|
23274
23701
|
if (legacy) {
|
|
23275
23702
|
const lines = existing.split(/\r?\n/);
|
|
23276
23703
|
const before = lines.slice(0, legacy.startLine).join("\n").trimEnd();
|
|
@@ -23279,39 +23706,39 @@ function replaceContextStreamBlock(existing, content) {
|
|
|
23279
23706
|
const merged = [before, replacement, after].filter((part) => part.length > 0).join("\n\n");
|
|
23280
23707
|
return { content: merged.trim() + "\n", status: "updated" };
|
|
23281
23708
|
}
|
|
23282
|
-
if (
|
|
23709
|
+
if (isLegacyContextStreamRules2(existing)) {
|
|
23283
23710
|
return { content: fullWrapped + "\n", status: "updated" };
|
|
23284
23711
|
}
|
|
23285
23712
|
const appended = existing.trimEnd() + "\n\n" + blockWrapped + "\n";
|
|
23286
23713
|
return { content: appended, status: "appended" };
|
|
23287
23714
|
}
|
|
23288
23715
|
async function upsertRuleFile(filePath, content, writeMode = "contextstream_block") {
|
|
23289
|
-
await
|
|
23716
|
+
await fs9.promises.mkdir(path11.dirname(filePath), { recursive: true });
|
|
23290
23717
|
if (writeMode === "full_file") {
|
|
23291
23718
|
let existing2 = "";
|
|
23292
23719
|
try {
|
|
23293
|
-
existing2 = await
|
|
23720
|
+
existing2 = await fs9.promises.readFile(filePath, "utf8");
|
|
23294
23721
|
} catch {
|
|
23295
23722
|
}
|
|
23296
|
-
await
|
|
23723
|
+
await fs9.promises.writeFile(filePath, content.trimEnd() + "\n", "utf8");
|
|
23297
23724
|
return existing2 ? "updated" : "created";
|
|
23298
23725
|
}
|
|
23299
|
-
const wrappedContent =
|
|
23726
|
+
const wrappedContent = wrapWithMarkers2(content);
|
|
23300
23727
|
let existing = "";
|
|
23301
23728
|
try {
|
|
23302
|
-
existing = await
|
|
23729
|
+
existing = await fs9.promises.readFile(filePath, "utf8");
|
|
23303
23730
|
} catch {
|
|
23304
23731
|
}
|
|
23305
23732
|
if (!existing) {
|
|
23306
|
-
await
|
|
23733
|
+
await fs9.promises.writeFile(filePath, wrappedContent + "\n", "utf8");
|
|
23307
23734
|
return "created";
|
|
23308
23735
|
}
|
|
23309
23736
|
if (!existing.trim()) {
|
|
23310
|
-
await
|
|
23737
|
+
await fs9.promises.writeFile(filePath, wrappedContent + "\n", "utf8");
|
|
23311
23738
|
return "updated";
|
|
23312
23739
|
}
|
|
23313
|
-
const replaced =
|
|
23314
|
-
await
|
|
23740
|
+
const replaced = replaceContextStreamBlock2(existing, content);
|
|
23741
|
+
await fs9.promises.writeFile(filePath, replaced.content, "utf8");
|
|
23315
23742
|
return replaced.status;
|
|
23316
23743
|
}
|
|
23317
23744
|
async function writeEditorRules(options) {
|
|
@@ -23330,8 +23757,8 @@ async function writeEditorRules(options) {
|
|
|
23330
23757
|
continue;
|
|
23331
23758
|
}
|
|
23332
23759
|
for (const rule of ruleFiles) {
|
|
23333
|
-
const filePath =
|
|
23334
|
-
if (
|
|
23760
|
+
const filePath = path11.join(options.folderPath, rule.filename);
|
|
23761
|
+
if (fs9.existsSync(filePath) && !options.overwriteExisting) {
|
|
23335
23762
|
results.push({ editor, filename: rule.filename, status: "skipped (exists)" });
|
|
23336
23763
|
continue;
|
|
23337
23764
|
}
|
|
@@ -23388,7 +23815,7 @@ async function writeGlobalRules(options) {
|
|
|
23388
23815
|
continue;
|
|
23389
23816
|
}
|
|
23390
23817
|
for (const filePath of globalPaths) {
|
|
23391
|
-
if (
|
|
23818
|
+
if (fs9.existsSync(filePath) && !options.overwriteExisting) {
|
|
23392
23819
|
results.push({ editor, filename: filePath, status: "skipped (exists)", scope: "global" });
|
|
23393
23820
|
continue;
|
|
23394
23821
|
}
|
|
@@ -24578,6 +25005,27 @@ function scoreConfidenceBand(score) {
|
|
|
24578
25005
|
if (score >= 0.6) return "medium";
|
|
24579
25006
|
return "low";
|
|
24580
25007
|
}
|
|
25008
|
+
function isExactIdentifierQuery(query) {
|
|
25009
|
+
const trimmed = query.trim();
|
|
25010
|
+
if (!trimmed || trimmed.length < 2 || trimmed.includes(" ")) return false;
|
|
25011
|
+
if (!/^[A-Za-z0-9_:]+$/.test(trimmed)) return false;
|
|
25012
|
+
const hasMixedCase = /[a-z]/.test(trimmed) && /[A-Z]/.test(trimmed);
|
|
25013
|
+
const hasUnderscore = trimmed.includes("_");
|
|
25014
|
+
const isAllCaps = trimmed.length >= 3 && /^[A-Z0-9_]+$/.test(trimmed);
|
|
25015
|
+
return hasMixedCase || hasUnderscore || isAllCaps;
|
|
25016
|
+
}
|
|
25017
|
+
function exactQuotedLiteral(query) {
|
|
25018
|
+
const trimmed = query.trim();
|
|
25019
|
+
if (trimmed.length < 2) return void 0;
|
|
25020
|
+
const isDoubleQuoted = trimmed.startsWith('"') && trimmed.endsWith('"');
|
|
25021
|
+
const isSingleQuoted = trimmed.startsWith("'") && trimmed.endsWith("'");
|
|
25022
|
+
if (!isDoubleQuoted && !isSingleQuoted) return void 0;
|
|
25023
|
+
const literal = trimmed.slice(1, -1).trim();
|
|
25024
|
+
return literal || void 0;
|
|
25025
|
+
}
|
|
25026
|
+
function allowBroadSearchFallbacks(modeAutoSelected, query) {
|
|
25027
|
+
return modeAutoSelected && !isExactIdentifierQuery(query) && exactQuotedLiteral(query) === void 0;
|
|
25028
|
+
}
|
|
24581
25029
|
function queryHasCodeIntent(query) {
|
|
24582
25030
|
const trimmed = query.trim();
|
|
24583
25031
|
if (!trimmed) return false;
|
|
@@ -25114,7 +25562,7 @@ async function resolveTargetProjectId(client, workspaceId, targetProject) {
|
|
|
25114
25562
|
const names = [
|
|
25115
25563
|
project?.name,
|
|
25116
25564
|
project?.slug,
|
|
25117
|
-
project?.path ?
|
|
25565
|
+
project?.path ? path11.basename(String(project.path)) : void 0
|
|
25118
25566
|
].filter((item) => typeof item === "string").map((item) => item.trim().toLowerCase());
|
|
25119
25567
|
return names.includes(normalized);
|
|
25120
25568
|
});
|
|
@@ -26085,7 +26533,7 @@ Hint: Run session_init(folder_path="<your_project_path>") first to establish a s
|
|
|
26085
26533
|
);
|
|
26086
26534
|
}
|
|
26087
26535
|
function projectScopeRequiredResult(action, workspaceId, folderPath) {
|
|
26088
|
-
const suggestedProjectName = folderPath ?
|
|
26536
|
+
const suggestedProjectName = folderPath ? path11.basename(folderPath) || "default-project" : "default-project";
|
|
26089
26537
|
const createCommand = workspaceId ? folderPath ? `project(action="create", name="${suggestedProjectName}", workspace_id="${workspaceId}", folder_path="${folderPath}")` : `project(action="create", name="${suggestedProjectName}", workspace_id="${workspaceId}")` : folderPath ? `init(folder_path="${folderPath}")` : `init(folder_path="<your_project_path>")`;
|
|
26090
26538
|
const scopeHint = workspaceId ? "Workspace-only fallback is active for this folder, but this action requires a project scope." : "No workspace/project scope is currently resolved for this folder.";
|
|
26091
26539
|
return errorResult(
|
|
@@ -26270,16 +26718,7 @@ Next step: ${createCommand}`
|
|
|
26270
26718
|
return max;
|
|
26271
26719
|
}
|
|
26272
26720
|
function extractQuotedLiteral(query) {
|
|
26273
|
-
|
|
26274
|
-
if (trimmed.length < 2) return void 0;
|
|
26275
|
-
const isDoubleQuoted = trimmed.startsWith('"') && trimmed.endsWith('"');
|
|
26276
|
-
const isSingleQuoted = trimmed.startsWith("'") && trimmed.endsWith("'");
|
|
26277
|
-
if (!isDoubleQuoted && !isSingleQuoted) return void 0;
|
|
26278
|
-
const literal = trimmed.slice(1, -1).trim();
|
|
26279
|
-
return literal || void 0;
|
|
26280
|
-
}
|
|
26281
|
-
function escapeRegexLiteral(input) {
|
|
26282
|
-
return input.replace(/[\\.+*?()[\]{}|^$]/g, "\\$&");
|
|
26721
|
+
return exactQuotedLiteral(query);
|
|
26283
26722
|
}
|
|
26284
26723
|
const COUNT_QUERY_PREFIXES = ["how many ", "count ", "count of ", "number of ", "total "];
|
|
26285
26724
|
const ALL_MATCH_KEYWORDS = [
|
|
@@ -26315,13 +26754,7 @@ Next step: ${createCommand}`
|
|
|
26315
26754
|
return containsCodeIdentifiers(query) || isIdentifierQuery(query) ? 0.04 : SEMANTIC_SWITCH_MIN_IMPROVEMENT;
|
|
26316
26755
|
}
|
|
26317
26756
|
function isIdentifierQuery(query) {
|
|
26318
|
-
|
|
26319
|
-
if (!trimmed || trimmed.length < 2 || trimmed.includes(" ")) return false;
|
|
26320
|
-
if (!/^[A-Za-z0-9_:]+$/.test(trimmed)) return false;
|
|
26321
|
-
const hasMixedCase = /[a-z]/.test(trimmed) && /[A-Z]/.test(trimmed);
|
|
26322
|
-
const hasUnderscore = trimmed.includes("_");
|
|
26323
|
-
const isAllCaps = trimmed.length >= 3 && /^[A-Z0-9_]+$/.test(trimmed);
|
|
26324
|
-
return hasMixedCase || hasUnderscore || isAllCaps;
|
|
26757
|
+
return isExactIdentifierQuery(query);
|
|
26325
26758
|
}
|
|
26326
26759
|
function hasRegexCharacters(query) {
|
|
26327
26760
|
const trimmed = query.trim();
|
|
@@ -26602,12 +27035,12 @@ Next step: ${createCommand}`
|
|
|
26602
27035
|
async function indexedProjectIdForFolder(folderPath) {
|
|
26603
27036
|
const status = await readIndexStatus();
|
|
26604
27037
|
const projects = status.projects ?? {};
|
|
26605
|
-
const resolvedFolder =
|
|
27038
|
+
const resolvedFolder = path11.resolve(folderPath);
|
|
26606
27039
|
let bestMatch;
|
|
26607
27040
|
let currentRemotePromise;
|
|
26608
27041
|
for (const [projectPath, info] of Object.entries(projects)) {
|
|
26609
|
-
const resolvedProjectPath =
|
|
26610
|
-
const matches = resolvedFolder === resolvedProjectPath || resolvedFolder.startsWith(`${resolvedProjectPath}${
|
|
27042
|
+
const resolvedProjectPath = path11.resolve(projectPath);
|
|
27043
|
+
const matches = resolvedFolder === resolvedProjectPath || resolvedFolder.startsWith(`${resolvedProjectPath}${path11.sep}`) || resolvedProjectPath.startsWith(`${resolvedFolder}${path11.sep}`);
|
|
26611
27044
|
if (!matches) continue;
|
|
26612
27045
|
if (!info?.indexed_at) continue;
|
|
26613
27046
|
if (info?.git_remote) {
|
|
@@ -26677,10 +27110,22 @@ Next step: ${createCommand}`
|
|
|
26677
27110
|
}
|
|
26678
27111
|
}
|
|
26679
27112
|
async function validateReadableDirectory(inputPath) {
|
|
26680
|
-
const resolvedPath =
|
|
27113
|
+
const resolvedPath = path11.resolve(inputPath);
|
|
27114
|
+
if (isInvariantIgnoredPath(resolvedPath)) {
|
|
27115
|
+
return {
|
|
27116
|
+
ok: false,
|
|
27117
|
+
error: `Error: refusing to index a path inside agent state or a credential store: ${inputPath}. Pass the actual repository checkout instead.`
|
|
27118
|
+
};
|
|
27119
|
+
}
|
|
27120
|
+
if (isBroadProjectPath(resolvedPath) && process.env.CONTEXTSTREAM_ALLOW_BROAD_INGEST !== "1") {
|
|
27121
|
+
return {
|
|
27122
|
+
ok: false,
|
|
27123
|
+
error: `Error: refusing to index filesystem root or home directory: ${inputPath}. Pass a narrower project path, or set CONTEXTSTREAM_ALLOW_BROAD_INGEST=1 for an intentional broad ingest.`
|
|
27124
|
+
};
|
|
27125
|
+
}
|
|
26681
27126
|
let stats;
|
|
26682
27127
|
try {
|
|
26683
|
-
stats = await
|
|
27128
|
+
stats = await fs9.promises.stat(resolvedPath);
|
|
26684
27129
|
} catch (error) {
|
|
26685
27130
|
if (error?.code === "ENOENT") {
|
|
26686
27131
|
return { ok: false, error: `Error: path does not exist: ${inputPath}` };
|
|
@@ -26694,7 +27139,7 @@ Next step: ${createCommand}`
|
|
|
26694
27139
|
return { ok: false, error: `Error: path is not a directory: ${inputPath}` };
|
|
26695
27140
|
}
|
|
26696
27141
|
try {
|
|
26697
|
-
await
|
|
27142
|
+
await fs9.promises.access(resolvedPath, fs9.constants.R_OK | fs9.constants.X_OK);
|
|
26698
27143
|
} catch (error) {
|
|
26699
27144
|
return {
|
|
26700
27145
|
ok: false,
|
|
@@ -26703,6 +27148,40 @@ Next step: ${createCommand}`
|
|
|
26703
27148
|
}
|
|
26704
27149
|
return { ok: true, resolvedPath };
|
|
26705
27150
|
}
|
|
27151
|
+
async function remoteIndexStateResult(action, projectId, requestedPath) {
|
|
27152
|
+
let statusResult = {};
|
|
27153
|
+
try {
|
|
27154
|
+
statusResult = await client.projectIndexStatus(projectId);
|
|
27155
|
+
} catch (error) {
|
|
27156
|
+
if (!isNotFoundError(error)) throw error;
|
|
27157
|
+
}
|
|
27158
|
+
const status = classifyRemoteIndexState(statusResult);
|
|
27159
|
+
const filesIndexed = extractIndexedFileCount(statusResult);
|
|
27160
|
+
const target = requestedPath ? `'${requestedPath}'` : "the project folder";
|
|
27161
|
+
const remoteNote = `This MCP process cannot read ${target}; no workstation files were ingested by this call.`;
|
|
27162
|
+
let message;
|
|
27163
|
+
if (status === "server_index_ready") {
|
|
27164
|
+
const countNote = filesIndexed !== void 0 ? ` (${filesIndexed} files)` : "";
|
|
27165
|
+
message = `Server-side index is ready for project ${projectId}${countNote}. ${remoteNote} Keep local changes current with the ContextStream sync bridge, editor hooks, or ContextStream Desktop, and verify freshness with project(action="index_status").`;
|
|
27166
|
+
} else if (status === "server_indexing") {
|
|
27167
|
+
message = `A server-side index run is already in progress for project ${projectId}. ${remoteNote} Monitor it with project(action="index_status").`;
|
|
27168
|
+
} else {
|
|
27169
|
+
message = `No exact-checkout content upload has completed for project ${projectId}. ${remoteNote} Keep hosted MCP configured and index from a process that can read the checkout, the ContextStream sync bridge, editor hooks, or ContextStream Desktop. Then retry project(action="index") and verify with project(action="index_status").`;
|
|
27170
|
+
}
|
|
27171
|
+
const structured = {
|
|
27172
|
+
status,
|
|
27173
|
+
invoked_action: action,
|
|
27174
|
+
project_id: projectId,
|
|
27175
|
+
path: requestedPath ?? null,
|
|
27176
|
+
local_path_readable: false,
|
|
27177
|
+
files_indexed: filesIndexed ?? null,
|
|
27178
|
+
message
|
|
27179
|
+
};
|
|
27180
|
+
return {
|
|
27181
|
+
content: [{ type: "text", text: message }],
|
|
27182
|
+
structuredContent: structured
|
|
27183
|
+
};
|
|
27184
|
+
}
|
|
26706
27185
|
function startBackgroundIngest(projectId, resolvedPath, ingestOptions, options2 = {}) {
|
|
26707
27186
|
void (async () => {
|
|
26708
27187
|
const [gitHead, gitRemote] = await Promise.all([
|
|
@@ -26733,6 +27212,9 @@ Next step: ${createCommand}`
|
|
|
26733
27212
|
force: ingestOptions.force,
|
|
26734
27213
|
ingestOptions
|
|
26735
27214
|
});
|
|
27215
|
+
if (result.status === "error") {
|
|
27216
|
+
throw new Error(result.lastError || "all changed-file ingest batches failed");
|
|
27217
|
+
}
|
|
26736
27218
|
logTool(
|
|
26737
27219
|
"ingest",
|
|
26738
27220
|
"done",
|
|
@@ -27098,9 +27580,10 @@ ${formatContent(result)}`
|
|
|
27098
27580
|
inputSchema: external_exports.object({})
|
|
27099
27581
|
},
|
|
27100
27582
|
async () => {
|
|
27101
|
-
const result =
|
|
27583
|
+
const result = await loadMcpVersionInfo(client);
|
|
27102
27584
|
return {
|
|
27103
|
-
content: [{ type: "text", text: formatContent(result) }]
|
|
27585
|
+
content: [{ type: "text", text: formatContent(result) }],
|
|
27586
|
+
structuredContent: result
|
|
27104
27587
|
};
|
|
27105
27588
|
}
|
|
27106
27589
|
);
|
|
@@ -27695,17 +28178,17 @@ Access: Free`,
|
|
|
27695
28178
|
let rulesSkipped = [];
|
|
27696
28179
|
if (input.folder_path && projectData.id) {
|
|
27697
28180
|
try {
|
|
27698
|
-
const configDir =
|
|
27699
|
-
const configPath =
|
|
27700
|
-
if (!
|
|
27701
|
-
|
|
28181
|
+
const configDir = path11.join(input.folder_path, ".contextstream");
|
|
28182
|
+
const configPath = path11.join(configDir, "config.json");
|
|
28183
|
+
if (!fs9.existsSync(configDir)) {
|
|
28184
|
+
fs9.mkdirSync(configDir, { recursive: true });
|
|
27702
28185
|
}
|
|
27703
28186
|
const config = {
|
|
27704
28187
|
workspace_id: workspaceId,
|
|
27705
28188
|
project_id: projectData.id,
|
|
27706
28189
|
project_name: input.name
|
|
27707
28190
|
};
|
|
27708
|
-
|
|
28191
|
+
fs9.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
27709
28192
|
if (input.generate_editor_rules) {
|
|
27710
28193
|
const ruleResults = await writeEditorRules({
|
|
27711
28194
|
folderPath: input.folder_path,
|
|
@@ -29291,7 +29774,7 @@ This does semantic search on the first message. You only need context on subsequ
|
|
|
29291
29774
|
formatContent(result)
|
|
29292
29775
|
].join("\n");
|
|
29293
29776
|
} else if (status === "requires_workspace_selection") {
|
|
29294
|
-
const folderName = typeof result.folder_name === "string" ? result.folder_name : typeof input.folder_path === "string" ?
|
|
29777
|
+
const folderName = typeof result.folder_name === "string" ? result.folder_name : typeof input.folder_path === "string" ? path11.basename(input.folder_path) || "this folder" : "this folder";
|
|
29295
29778
|
const candidates = Array.isArray(result.workspace_candidates) ? result.workspace_candidates : [];
|
|
29296
29779
|
const lines = [];
|
|
29297
29780
|
lines.push(
|
|
@@ -29544,7 +30027,17 @@ Behavior:
|
|
|
29544
30027
|
"Error: folder_path is required. Provide folder_path or run from a project directory."
|
|
29545
30028
|
);
|
|
29546
30029
|
}
|
|
29547
|
-
|
|
30030
|
+
if (isInvariantIgnoredPath(folderPath)) {
|
|
30031
|
+
return errorResult(
|
|
30032
|
+
`Refusing to bootstrap a path inside agent state or a credential store: ${folderPath}. Pass the actual repository checkout instead.`
|
|
30033
|
+
);
|
|
30034
|
+
}
|
|
30035
|
+
if (isBroadProjectPath(folderPath) && process.env.CONTEXTSTREAM_ALLOW_BROAD_INGEST !== "1") {
|
|
30036
|
+
return errorResult(
|
|
30037
|
+
`Refusing to bootstrap filesystem root or home directory as a project: ${folderPath}. Pass a narrower repository/project path.`
|
|
30038
|
+
);
|
|
30039
|
+
}
|
|
30040
|
+
const folderName = path11.basename(folderPath) || "My Project";
|
|
29548
30041
|
let newWorkspace;
|
|
29549
30042
|
try {
|
|
29550
30043
|
newWorkspace = await client.createWorkspace({
|
|
@@ -32383,6 +32876,10 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
32383
32876
|
const modeRecommendation = recommendSearchMode(input.query);
|
|
32384
32877
|
const modeAutoSelected = modeInput === "auto";
|
|
32385
32878
|
const requestedMode = modeAutoSelected ? modeRecommendation.mode : modeInput;
|
|
32879
|
+
const broadSearchFallbacksAllowed = allowBroadSearchFallbacks(
|
|
32880
|
+
modeAutoSelected,
|
|
32881
|
+
input.query
|
|
32882
|
+
);
|
|
32386
32883
|
let workspaceId = resolveWorkspaceId(input.workspace_id);
|
|
32387
32884
|
const sessionProjectId = resolveProjectId(void 0);
|
|
32388
32885
|
const requestedExplicitProjectId = normalizeUuid(input.project_id);
|
|
@@ -32446,7 +32943,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
32446
32943
|
crawl: "search_crawl",
|
|
32447
32944
|
team: "search_hybrid"
|
|
32448
32945
|
};
|
|
32449
|
-
const runSearchForMode = async (mode, baseParams2) => {
|
|
32946
|
+
const runSearchForMode = async (mode, baseParams2, allowBroadFallbacks) => {
|
|
32450
32947
|
if (mode === "team") {
|
|
32451
32948
|
const isTeamPlanForSearch = await client.isTeamPlan();
|
|
32452
32949
|
if (!isTeamPlanForSearch) {
|
|
@@ -32494,8 +32991,26 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
32494
32991
|
let result;
|
|
32495
32992
|
let executedMode = dispatchMode;
|
|
32496
32993
|
let fallbackNote;
|
|
32994
|
+
const quotedLiteral = dispatchMode === "keyword" ? extractQuotedLiteral(input.query) : void 0;
|
|
32995
|
+
const identifierExhaustiveFastPath = dispatchMode === "keyword" && quotedLiteral === void 0 && baseParams2.include_memory !== true && isIdentifierQuery(baseParams2.query);
|
|
32996
|
+
const primaryParams = quotedLiteral ? { ...baseParams2, query: quotedLiteral } : baseParams2;
|
|
32497
32997
|
try {
|
|
32498
|
-
|
|
32998
|
+
if (identifierExhaustiveFastPath) {
|
|
32999
|
+
try {
|
|
33000
|
+
result = await executeSearchMode("exhaustive", baseParams2);
|
|
33001
|
+
executedMode = "exhaustive";
|
|
33002
|
+
fallbackNote = "Exact identifier query used the exhaustive literal fast path.";
|
|
33003
|
+
} catch {
|
|
33004
|
+
result = await executeSearchMode("keyword", baseParams2);
|
|
33005
|
+
executedMode = "keyword";
|
|
33006
|
+
fallbackNote = "Exact identifier exhaustive fast path was unavailable; used keyword search.";
|
|
33007
|
+
}
|
|
33008
|
+
} else {
|
|
33009
|
+
result = await executeSearchMode(dispatchMode, primaryParams);
|
|
33010
|
+
if (quotedLiteral) {
|
|
33011
|
+
fallbackNote = "Normalized surrounding quotes before exact keyword search.";
|
|
33012
|
+
}
|
|
33013
|
+
}
|
|
32499
33014
|
} catch (execError) {
|
|
32500
33015
|
const execMsg = execError instanceof Error ? execError.message : String(execError);
|
|
32501
33016
|
const isEmbedTimeout = execMsg.toLowerCase().includes("embedding timed out") || execMsg.toLowerCase().includes("embedding timeout");
|
|
@@ -32507,7 +33022,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
32507
33022
|
throw execError;
|
|
32508
33023
|
}
|
|
32509
33024
|
}
|
|
32510
|
-
if (shouldRetrySemanticFallback(input.query, dispatchMode, result)) {
|
|
33025
|
+
if (allowBroadFallbacks && shouldRetrySemanticFallback(input.query, dispatchMode, result)) {
|
|
32511
33026
|
try {
|
|
32512
33027
|
const semanticResult = await executeSearchMode("semantic", baseParams2);
|
|
32513
33028
|
if (shouldPreferSemanticResults(input.query, result, semanticResult)) {
|
|
@@ -32522,63 +33037,38 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
32522
33037
|
}
|
|
32523
33038
|
}
|
|
32524
33039
|
const currentResults = () => extractSearchEnvelope(result).results;
|
|
32525
|
-
if (
|
|
32526
|
-
|
|
32527
|
-
|
|
32528
|
-
|
|
32529
|
-
|
|
32530
|
-
|
|
32531
|
-
|
|
32532
|
-
|
|
32533
|
-
|
|
32534
|
-
|
|
32535
|
-
|
|
32536
|
-
|
|
32537
|
-
|
|
32538
|
-
fallbackNote,
|
|
32539
|
-
"Quoted keyword search returned no results; retried keyword search without quotes."
|
|
32540
|
-
);
|
|
32541
|
-
}
|
|
32542
|
-
} catch {
|
|
32543
|
-
}
|
|
32544
|
-
}
|
|
32545
|
-
if (currentResults().length === 0) {
|
|
32546
|
-
try {
|
|
32547
|
-
const patternResult = await executeSearchMode("pattern", {
|
|
32548
|
-
...baseParams2,
|
|
32549
|
-
query: escapeRegexLiteral(literal)
|
|
32550
|
-
});
|
|
32551
|
-
if (extractSearchEnvelope(patternResult).results.length > 0) {
|
|
32552
|
-
result = patternResult;
|
|
32553
|
-
executedMode = "pattern";
|
|
32554
|
-
fallbackNote = appendNote(
|
|
32555
|
-
fallbackNote,
|
|
32556
|
-
"Quoted keyword search returned no results; retried literal pattern search."
|
|
32557
|
-
);
|
|
32558
|
-
}
|
|
32559
|
-
} catch {
|
|
32560
|
-
}
|
|
33040
|
+
if (quotedLiteral && currentResults().length === 0) {
|
|
33041
|
+
try {
|
|
33042
|
+
const exhaustiveResult = await executeSearchMode("exhaustive", {
|
|
33043
|
+
...baseParams2,
|
|
33044
|
+
query: quotedLiteral
|
|
33045
|
+
});
|
|
33046
|
+
if (extractSearchEnvelope(exhaustiveResult).results.length > 0) {
|
|
33047
|
+
result = exhaustiveResult;
|
|
33048
|
+
executedMode = "exhaustive";
|
|
33049
|
+
fallbackNote = appendNote(
|
|
33050
|
+
fallbackNote,
|
|
33051
|
+
"Exact keyword search returned no results; retried exhaustive search for complete literal coverage."
|
|
33052
|
+
);
|
|
32561
33053
|
}
|
|
32562
|
-
|
|
32563
|
-
|
|
32564
|
-
|
|
32565
|
-
|
|
32566
|
-
|
|
32567
|
-
|
|
32568
|
-
|
|
32569
|
-
|
|
32570
|
-
|
|
32571
|
-
|
|
32572
|
-
|
|
32573
|
-
|
|
32574
|
-
|
|
32575
|
-
}
|
|
32576
|
-
} catch {
|
|
32577
|
-
}
|
|
33054
|
+
} catch {
|
|
33055
|
+
}
|
|
33056
|
+
}
|
|
33057
|
+
if (identifierExhaustiveFastPath && currentResults().length === 0) {
|
|
33058
|
+
try {
|
|
33059
|
+
const keywordResult = await executeSearchMode("keyword", baseParams2);
|
|
33060
|
+
if (extractSearchEnvelope(keywordResult).results.length > 0) {
|
|
33061
|
+
result = keywordResult;
|
|
33062
|
+
executedMode = "keyword";
|
|
33063
|
+
fallbackNote = appendNote(
|
|
33064
|
+
fallbackNote,
|
|
33065
|
+
"Exact identifier exhaustive lookup returned no results; keyword fallback found tokenized matches."
|
|
33066
|
+
);
|
|
32578
33067
|
}
|
|
33068
|
+
} catch {
|
|
32579
33069
|
}
|
|
32580
33070
|
}
|
|
32581
|
-
if ((dispatchMode === "refactor" || dispatchMode === "exhaustive") && currentResults().length === 0) {
|
|
33071
|
+
if (allowBroadFallbacks && (dispatchMode === "refactor" || dispatchMode === "exhaustive") && currentResults().length === 0) {
|
|
32582
33072
|
try {
|
|
32583
33073
|
const keywordResult = await executeSearchMode("keyword", baseParams2);
|
|
32584
33074
|
if (extractSearchEnvelope(keywordResult).results.length > 0) {
|
|
@@ -32606,7 +33096,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
32606
33096
|
}
|
|
32607
33097
|
} catch {
|
|
32608
33098
|
}
|
|
32609
|
-
if (currentResults().length === 0) {
|
|
33099
|
+
if (currentResults().length === 0 && !identifierExhaustiveFastPath) {
|
|
32610
33100
|
try {
|
|
32611
33101
|
const exhaustiveResult = await executeSearchMode("exhaustive", baseParams2);
|
|
32612
33102
|
if (extractSearchEnvelope(exhaustiveResult).results.length > 0) {
|
|
@@ -32621,7 +33111,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
32621
33111
|
}
|
|
32622
33112
|
}
|
|
32623
33113
|
}
|
|
32624
|
-
if (currentResults().length === 0 && shouldRetryKeywordWithSemantic(input.query)) {
|
|
33114
|
+
if (allowBroadFallbacks && currentResults().length === 0 && shouldRetryKeywordWithSemantic(input.query)) {
|
|
32625
33115
|
try {
|
|
32626
33116
|
const semanticResult = await executeSearchMode("semantic", baseParams2);
|
|
32627
33117
|
if (extractSearchEnvelope(semanticResult).results.length > 0) {
|
|
@@ -32635,7 +33125,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
32635
33125
|
} catch {
|
|
32636
33126
|
}
|
|
32637
33127
|
}
|
|
32638
|
-
if (currentResults().length === 0) {
|
|
33128
|
+
if (allowBroadFallbacks && currentResults().length === 0) {
|
|
32639
33129
|
try {
|
|
32640
33130
|
const hybridResult = await executeSearchMode("hybrid", baseParams2);
|
|
32641
33131
|
if (extractSearchEnvelope(hybridResult).results.length > 0) {
|
|
@@ -32675,10 +33165,14 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
32675
33165
|
}
|
|
32676
33166
|
if (requestedMode === "team") {
|
|
32677
33167
|
try {
|
|
32678
|
-
const teamResult = await runSearchForMode(
|
|
32679
|
-
|
|
32680
|
-
|
|
32681
|
-
|
|
33168
|
+
const teamResult = await runSearchForMode(
|
|
33169
|
+
"team",
|
|
33170
|
+
{
|
|
33171
|
+
...baseParams,
|
|
33172
|
+
project_id: void 0
|
|
33173
|
+
},
|
|
33174
|
+
broadSearchFallbacksAllowed
|
|
33175
|
+
);
|
|
32682
33176
|
selected = {
|
|
32683
33177
|
index: 0,
|
|
32684
33178
|
project_id: void 0,
|
|
@@ -32698,7 +33192,11 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
32698
33192
|
project_id: candidateProjectId
|
|
32699
33193
|
};
|
|
32700
33194
|
try {
|
|
32701
|
-
const modeResult = await runSearchForMode(
|
|
33195
|
+
const modeResult = await runSearchForMode(
|
|
33196
|
+
requestedMode,
|
|
33197
|
+
paramsForCandidate,
|
|
33198
|
+
broadSearchFallbacksAllowed
|
|
33199
|
+
);
|
|
32702
33200
|
const envelope = extractSearchEnvelope(modeResult.result);
|
|
32703
33201
|
if (isScopeInvalidResult(modeResult.result)) {
|
|
32704
33202
|
if (!selected) {
|
|
@@ -32742,7 +33240,11 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
32742
33240
|
const isEmbeddingTimeout = errMsg.toLowerCase().includes("embedding timed out") || errMsg.toLowerCase().includes("embedding timeout");
|
|
32743
33241
|
if (isEmbeddingTimeout && requestedMode !== "keyword" && requestedMode !== "exhaustive") {
|
|
32744
33242
|
try {
|
|
32745
|
-
const keywordFallback = await runSearchForMode(
|
|
33243
|
+
const keywordFallback = await runSearchForMode(
|
|
33244
|
+
"keyword",
|
|
33245
|
+
paramsForCandidate,
|
|
33246
|
+
false
|
|
33247
|
+
);
|
|
32746
33248
|
const kwEnvelope = extractSearchEnvelope(keywordFallback.result);
|
|
32747
33249
|
selected = {
|
|
32748
33250
|
index,
|
|
@@ -32842,9 +33344,9 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
32842
33344
|
const beforePrune = results.length;
|
|
32843
33345
|
results = results.filter((r) => {
|
|
32844
33346
|
if (typeof r?.file_path !== "string" || !r.file_path) return true;
|
|
32845
|
-
if (
|
|
33347
|
+
if (path11.isAbsolute(r.file_path)) return true;
|
|
32846
33348
|
try {
|
|
32847
|
-
return
|
|
33349
|
+
return fs9.existsSync(path11.join(folderPath, r.file_path));
|
|
32848
33350
|
} catch {
|
|
32849
33351
|
return true;
|
|
32850
33352
|
}
|
|
@@ -32857,7 +33359,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
32857
33359
|
}
|
|
32858
33360
|
try {
|
|
32859
33361
|
const indexStatusFile = await readIndexStatus();
|
|
32860
|
-
const indexEntry = indexStatusFile.projects?.[
|
|
33362
|
+
const indexEntry = indexStatusFile.projects?.[path11.resolve(folderPath)];
|
|
32861
33363
|
if (indexEntry?.indexed_at) {
|
|
32862
33364
|
const indexedAtMs = Date.parse(indexEntry.indexed_at);
|
|
32863
33365
|
const dirtyCount = await countDirtyFilesSince(
|
|
@@ -32874,7 +33376,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
32874
33376
|
} catch {
|
|
32875
33377
|
}
|
|
32876
33378
|
}
|
|
32877
|
-
if (results.length === 0 && !scopeInvalid) {
|
|
33379
|
+
if (results.length === 0 && !scopeInvalid && broadSearchFallbacksAllowed) {
|
|
32878
33380
|
const escalationModes = (() => {
|
|
32879
33381
|
switch (selected.executedMode) {
|
|
32880
33382
|
case "semantic":
|
|
@@ -32889,16 +33391,23 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
32889
33391
|
})();
|
|
32890
33392
|
for (const escMode of escalationModes) {
|
|
32891
33393
|
try {
|
|
32892
|
-
const escResult = await runSearchForMode(
|
|
32893
|
-
|
|
32894
|
-
|
|
32895
|
-
|
|
32896
|
-
|
|
33394
|
+
const escResult = await runSearchForMode(
|
|
33395
|
+
escMode,
|
|
33396
|
+
{
|
|
33397
|
+
...baseParams,
|
|
33398
|
+
workspace_id: workspaceId,
|
|
33399
|
+
project_id: selected.project_id
|
|
33400
|
+
},
|
|
33401
|
+
false
|
|
33402
|
+
);
|
|
32897
33403
|
const escEnvelope = extractSearchEnvelope(escResult.result);
|
|
32898
33404
|
if (escEnvelope.results.length > 0) {
|
|
32899
33405
|
results = escEnvelope.results;
|
|
32900
33406
|
total = escEnvelope.total;
|
|
32901
|
-
modeFallbackNote = appendNote(
|
|
33407
|
+
modeFallbackNote = appendNote(
|
|
33408
|
+
modeFallbackNote,
|
|
33409
|
+
`${selected.executedMode} returned 0 results; escalated to ${escMode}.`
|
|
33410
|
+
);
|
|
32902
33411
|
break;
|
|
32903
33412
|
}
|
|
32904
33413
|
} catch {
|
|
@@ -33251,7 +33760,7 @@ ${listing}`
|
|
|
33251
33760
|
"session",
|
|
33252
33761
|
{
|
|
33253
33762
|
title: "Session",
|
|
33254
|
-
description: `Session and memory management \u2014 NOT for codebase/file search (use the 'search' tool for that). LESSONS LIVE HERE: when a mistake or correction happens, call action='capture_lesson' (NEVER write lessons to ~/.claude/.../memory/, .cursorrules, or other local markdown \u2014 local files are invisible to [LESSONS_WARNING] auto-surfacing on future turns and across sessions). PAST SESSIONS LIVE HERE: use action='recall' FIRST when the user references "last time", "previous", "yesterday", or is continuing prior work \u2014 full-text transcripts are indexed across every prior session. context() may surface [GROUNDING]; use action='ground' with user_message for a one-shot bundle (recall + docs + decisions + lessons + skills) outside context(). Actions: capture (save decision/insight), retro_capture (after-the-fact decision/note/snapshot capture from prior work with source provenance \u2014 title plus content and/or query/transcript_ids), capture_lesson (mistakes/corrections \u2014 title+trigger+impact+prevention), get_lessons (retrieve lessons), update_lesson / delete_lesson (maintain a saved lesson by lesson_id \u2014 UUID or lookup text), recall (retrieve past conversation context via ranked fusion of transcripts/snapshots/docs/decisions), ground (one-shot prior-work bundle), remember (quick save), user_context (get preferences), summary (workspace summary), compress (compress chat), delta (changes since timestamp), smart_search (searches MEMORY/conversation history only, not code), decision_trace (trace decision provenance), restore_context (restore state after compaction). Plan actions: capture_plan, get_plan, update_plan, list_plans. Suggested rules actions: list_suggested_rules, suggested_rule_action, suggested_rules_stats. Team actions: team_decisions, team_lessons, team_plans. Team/personal mode: set_account_mode (team|personal|auto).`,
|
|
33763
|
+
description: `Session and memory management \u2014 NOT for codebase/file search (use the 'search' tool for that). LESSONS LIVE HERE: when a mistake or correction happens, call action='capture_lesson' (NEVER write lessons to ~/.claude/.../memory/, .cursorrules, or other local markdown \u2014 local files are invisible to [LESSONS_WARNING] auto-surfacing on future turns and across sessions). PAST SESSIONS LIVE HERE: use action='recall' FIRST when the user references "last time", "previous", "yesterday", or is continuing prior work \u2014 full-text transcripts are indexed across every prior session. context() may surface [GROUNDING]; use action='ground' with user_message for a one-shot bundle (recall + docs + decisions + lessons + skills) outside context(). Actions: capture (save decision/insight), retro_capture (after-the-fact decision/note/snapshot capture from prior work with source provenance \u2014 title plus content and/or query/transcript_ids), capture_lesson (mistakes/corrections \u2014 title+trigger+impact+prevention), get_lessons (retrieve lessons), update_lesson / delete_lesson (maintain a saved lesson by lesson_id \u2014 UUID or lookup text), recall (retrieve past conversation context via ranked fusion of transcripts/snapshots/docs/decisions), ground (one-shot prior-work bundle), remember (quick save), user_context (get preferences), summary (workspace summary), compress (compress chat), delta (changes since timestamp), smart_search (searches MEMORY/conversation history only, not code), decision_trace (trace decision provenance), list_recaps (timestamped Daily Recap history), trigger_recap (manual asynchronous Daily Recap generation), restore_context (restore state after compaction). Daily Recaps run around 23:00 in the user's timezone, not at MCP session boundaries. Plan actions: capture_plan, get_plan, update_plan, list_plans. Suggested rules actions: list_suggested_rules, suggested_rule_action, suggested_rules_stats. Team actions: team_decisions, team_lessons, team_plans. Team/personal mode: set_account_mode (team|personal|auto).`,
|
|
33255
33764
|
inputSchema: external_exports.object({
|
|
33256
33765
|
action: external_exports.enum([
|
|
33257
33766
|
"capture",
|
|
@@ -33270,6 +33779,8 @@ ${listing}`
|
|
|
33270
33779
|
"delta",
|
|
33271
33780
|
"smart_search",
|
|
33272
33781
|
"decision_trace",
|
|
33782
|
+
"list_recaps",
|
|
33783
|
+
"trigger_recap",
|
|
33273
33784
|
// Plan actions
|
|
33274
33785
|
"capture_plan",
|
|
33275
33786
|
"get_plan",
|
|
@@ -34043,6 +34554,32 @@ ${listing}`
|
|
|
34043
34554
|
};
|
|
34044
34555
|
}
|
|
34045
34556
|
}
|
|
34557
|
+
case "list_recaps": {
|
|
34558
|
+
if (!workspaceId) {
|
|
34559
|
+
return errorResult("list_recaps requires workspace_id. Call init first or pass workspace_id explicitly.");
|
|
34560
|
+
}
|
|
34561
|
+
const result = await client.listDailyRecaps({
|
|
34562
|
+
workspace_id: workspaceId,
|
|
34563
|
+
limit: input.limit
|
|
34564
|
+
});
|
|
34565
|
+
return {
|
|
34566
|
+
content: [{ type: "text", text: formatDailyRecaps(result) }],
|
|
34567
|
+
structuredContent: toStructured(result)
|
|
34568
|
+
};
|
|
34569
|
+
}
|
|
34570
|
+
case "trigger_recap": {
|
|
34571
|
+
if (!workspaceId) {
|
|
34572
|
+
return errorResult("trigger_recap requires workspace_id. Call init first or pass workspace_id explicitly.");
|
|
34573
|
+
}
|
|
34574
|
+
const result = await client.triggerDailyRecap({ workspace_id: workspaceId });
|
|
34575
|
+
return {
|
|
34576
|
+
content: [{
|
|
34577
|
+
type: "text",
|
|
34578
|
+
text: `Daily Recap generation queued. Automatic recaps also run around 23:00 in the user's configured timezone; generation is not tied to MCP session boundaries. Use session(action="list_recaps") to verify the completed recap and its generated_at timestamp.`
|
|
34579
|
+
}],
|
|
34580
|
+
structuredContent: toStructured(result)
|
|
34581
|
+
};
|
|
34582
|
+
}
|
|
34046
34583
|
// Plan actions
|
|
34047
34584
|
case "capture_plan": {
|
|
34048
34585
|
if (!input.title) {
|
|
@@ -34503,15 +35040,19 @@ ${formatContent(result)}` : formatContent(result);
|
|
|
34503
35040
|
registerTool(
|
|
34504
35041
|
"entity",
|
|
34505
35042
|
{
|
|
34506
|
-
title: "Structured
|
|
34507
|
-
description:
|
|
35043
|
+
title: "Structured Handoffs, Tickets, and Workflow Entities",
|
|
35044
|
+
description: ENTITY_TOOL_DESCRIPTION,
|
|
34508
35045
|
inputSchema: external_exports.object({
|
|
34509
|
-
kind: external_exports.enum(VALID_ENTITY_KINDS).describe(
|
|
35046
|
+
kind: external_exports.enum(VALID_ENTITY_KINDS).describe(
|
|
35047
|
+
"Entity kind. handoff is the canonical durable agent/session handoff; capsule is an optional additional portable artifact."
|
|
35048
|
+
),
|
|
34510
35049
|
action: external_exports.enum(["list", "get", "create", "update", "delete"]).describe("Action to perform"),
|
|
34511
35050
|
id: external_exports.string().uuid().optional().describe("Entity ID (required for get / update / delete)"),
|
|
34512
35051
|
workspace_id: external_exports.string().uuid().optional(),
|
|
34513
35052
|
project_id: external_exports.string().uuid().optional(),
|
|
34514
|
-
body: external_exports.record(external_exports.any()).optional().describe(
|
|
35053
|
+
body: external_exports.record(external_exports.any()).optional().describe(
|
|
35054
|
+
"JSON body for create/update. For handoff create, include title, summary, scope, and next_steps; omit to_user_id when the recipient is unknown."
|
|
35055
|
+
),
|
|
34515
35056
|
query: external_exports.record(external_exports.any()).optional().describe("Filter params for list")
|
|
34516
35057
|
})
|
|
34517
35058
|
},
|
|
@@ -34584,7 +35125,7 @@ ${formatContent(result)}` }],
|
|
|
34584
35125
|
"capsule",
|
|
34585
35126
|
{
|
|
34586
35127
|
title: "ContextCapsule",
|
|
34587
|
-
description:
|
|
35128
|
+
description: CAPSULE_TOOL_DESCRIPTION,
|
|
34588
35129
|
inputSchema: external_exports.object({
|
|
34589
35130
|
action: external_exports.enum(CAPSULE_ACTIONS).describe("Action to perform"),
|
|
34590
35131
|
capsule_id: external_exports.string().optional().describe("ContextCapsule ID"),
|
|
@@ -35807,8 +36348,7 @@ Pass the node_id UUID to delete one, or pass delete_all=true to remove ALL exact
|
|
|
35807
36348
|
return errorResult("update_task requires: task_id");
|
|
35808
36349
|
}
|
|
35809
36350
|
const requestedTaskStatus = input.task_status ?? input.status;
|
|
35810
|
-
const
|
|
35811
|
-
task_id: input.task_id,
|
|
36351
|
+
const taskUpdate = {
|
|
35812
36352
|
title: input.title,
|
|
35813
36353
|
content: input.content,
|
|
35814
36354
|
description: input.description,
|
|
@@ -35820,6 +36360,31 @@ Pass the node_id UUID to delete one, or pass delete_all=true to remove ALL exact
|
|
|
35820
36360
|
code_refs: input.code_refs,
|
|
35821
36361
|
tags: input.tags,
|
|
35822
36362
|
blocked_reason: input.blocked_reason
|
|
36363
|
+
};
|
|
36364
|
+
if (taskUpdateIsStatusOnly(taskUpdate)) {
|
|
36365
|
+
const current = await client.getTask({ task_id: input.task_id });
|
|
36366
|
+
const currentStatus = extractTaskStatus(current);
|
|
36367
|
+
if (currentStatus?.toLowerCase() === requestedTaskStatus?.toLowerCase()) {
|
|
36368
|
+
const result2 = {
|
|
36369
|
+
...current,
|
|
36370
|
+
operation_status: {
|
|
36371
|
+
operation: "update_task",
|
|
36372
|
+
state: "completed",
|
|
36373
|
+
changed: false,
|
|
36374
|
+
reason: "already_in_target_status",
|
|
36375
|
+
status: requestedTaskStatus
|
|
36376
|
+
},
|
|
36377
|
+
hint: `Task is already ${requestedTaskStatus}. No changes were needed.`
|
|
36378
|
+
};
|
|
36379
|
+
return {
|
|
36380
|
+
content: [{ type: "text", text: formatContent(result2) }],
|
|
36381
|
+
structuredContent: result2
|
|
36382
|
+
};
|
|
36383
|
+
}
|
|
36384
|
+
}
|
|
36385
|
+
const result = await client.updateTask({
|
|
36386
|
+
task_id: input.task_id,
|
|
36387
|
+
...taskUpdate
|
|
35823
36388
|
});
|
|
35824
36389
|
let taskUpdateHint = "Task updated.";
|
|
35825
36390
|
if (requestedTaskStatus === "completed") {
|
|
@@ -36842,6 +37407,41 @@ ${formatContent({
|
|
|
36842
37407
|
}
|
|
36843
37408
|
try {
|
|
36844
37409
|
const result = await client.indexProject(projectId);
|
|
37410
|
+
if (apiIndexResultIsSkipped(result)) {
|
|
37411
|
+
if (!folderPath) {
|
|
37412
|
+
return remoteIndexStateResult("index", projectId);
|
|
37413
|
+
}
|
|
37414
|
+
const validPath = await validateReadableDirectory(folderPath);
|
|
37415
|
+
if (!validPath.ok) {
|
|
37416
|
+
if (isInvariantIgnoredPath(folderPath) || isBroadProjectPath(folderPath) && process.env.CONTEXTSTREAM_ALLOW_BROAD_INGEST !== "1") {
|
|
37417
|
+
return errorResult(validPath.error);
|
|
37418
|
+
}
|
|
37419
|
+
return remoteIndexStateResult("index", projectId, folderPath);
|
|
37420
|
+
}
|
|
37421
|
+
const ingestOptions = {
|
|
37422
|
+
...input.write_to_disk !== void 0 && { write_to_disk: input.write_to_disk },
|
|
37423
|
+
...input.overwrite !== void 0 && { overwrite: input.overwrite },
|
|
37424
|
+
...input.force !== void 0 && { force: input.force }
|
|
37425
|
+
};
|
|
37426
|
+
startBackgroundIngest(projectId, validPath.resolvedPath, ingestOptions);
|
|
37427
|
+
const response = {
|
|
37428
|
+
status: "started",
|
|
37429
|
+
message: "Index endpoint skipped this local project type; started local ingest in the background",
|
|
37430
|
+
project_id: projectId,
|
|
37431
|
+
path: validPath.resolvedPath,
|
|
37432
|
+
fallback_action: "ingest_local",
|
|
37433
|
+
note: "Use 'project' with action 'index_status' to monitor progress."
|
|
37434
|
+
};
|
|
37435
|
+
return {
|
|
37436
|
+
content: [
|
|
37437
|
+
{
|
|
37438
|
+
type: "text",
|
|
37439
|
+
text: `Index endpoint skipped this local project type. Started local ingest in the background for directory: ${validPath.resolvedPath}. Use 'project' with action 'index_status' to monitor progress.`
|
|
37440
|
+
}
|
|
37441
|
+
],
|
|
37442
|
+
structuredContent: response
|
|
37443
|
+
};
|
|
37444
|
+
}
|
|
36845
37445
|
return {
|
|
36846
37446
|
content: [{ type: "text", text: formatContent(result) }]
|
|
36847
37447
|
};
|
|
@@ -36850,15 +37450,14 @@ ${formatContent({
|
|
|
36850
37450
|
throw error;
|
|
36851
37451
|
}
|
|
36852
37452
|
if (!folderPath) {
|
|
36853
|
-
return
|
|
36854
|
-
'Index endpoint is unavailable for this local project type and no folder context is set. Run project(action="ingest_local", path="<folder>").'
|
|
36855
|
-
);
|
|
37453
|
+
return remoteIndexStateResult("index", projectId);
|
|
36856
37454
|
}
|
|
36857
37455
|
const validPath = await validateReadableDirectory(folderPath);
|
|
36858
37456
|
if (!validPath.ok) {
|
|
36859
|
-
|
|
36860
|
-
|
|
36861
|
-
|
|
37457
|
+
if (isInvariantIgnoredPath(folderPath) || isBroadProjectPath(folderPath) && process.env.CONTEXTSTREAM_ALLOW_BROAD_INGEST !== "1") {
|
|
37458
|
+
return errorResult(validPath.error);
|
|
37459
|
+
}
|
|
37460
|
+
return remoteIndexStateResult("index", projectId, folderPath);
|
|
36862
37461
|
}
|
|
36863
37462
|
const ingestOptions = {
|
|
36864
37463
|
...input.write_to_disk !== void 0 && { write_to_disk: input.write_to_disk },
|
|
@@ -37108,18 +37707,26 @@ ${formatContent(response)}`
|
|
|
37108
37707
|
}
|
|
37109
37708
|
const validPath = await validateReadableDirectory(ingestPath);
|
|
37110
37709
|
if (!validPath.ok) {
|
|
37111
|
-
|
|
37112
|
-
|
|
37113
|
-
if (
|
|
37114
|
-
|
|
37115
|
-
|
|
37116
|
-
|
|
37117
|
-
|
|
37118
|
-
|
|
37119
|
-
|
|
37120
|
-
|
|
37710
|
+
const pathExists = await fs9.promises.stat(ingestPath).then(() => true).catch(() => false);
|
|
37711
|
+
if (!pathExists) {
|
|
37712
|
+
if (client.canDelegatePathIngest()) {
|
|
37713
|
+
try {
|
|
37714
|
+
const apiResult = await client.ingestFromPath(projectId, ingestPath, {
|
|
37715
|
+
force: input.force
|
|
37716
|
+
});
|
|
37717
|
+
return {
|
|
37718
|
+
content: [
|
|
37719
|
+
{
|
|
37720
|
+
type: "text",
|
|
37721
|
+
text: `Delegated ingest to API for path: ${ingestPath}
|
|
37722
|
+
${formatContent(apiResult)}`
|
|
37723
|
+
}
|
|
37724
|
+
]
|
|
37725
|
+
};
|
|
37726
|
+
} catch {
|
|
37727
|
+
}
|
|
37121
37728
|
}
|
|
37122
|
-
|
|
37729
|
+
return remoteIndexStateResult("ingest_local", projectId, ingestPath);
|
|
37123
37730
|
}
|
|
37124
37731
|
return errorResult(validPath.error);
|
|
37125
37732
|
}
|
|
@@ -37243,7 +37850,7 @@ Uncommitted changes: ${changes.diff_stat.summary}
|
|
|
37243
37850
|
};
|
|
37244
37851
|
}
|
|
37245
37852
|
case "forget_local": {
|
|
37246
|
-
const forgetPath =
|
|
37853
|
+
const forgetPath = path11.resolve(input.folder_path || folderPath || process.cwd());
|
|
37247
37854
|
const outcome = forgetLocalConfig(forgetPath);
|
|
37248
37855
|
const lines = [
|
|
37249
37856
|
outcome.local_config_removed ? `\u2713 Removed local scope binding for ${forgetPath}.` : `No local scope binding found for ${forgetPath} (nothing to remove).`,
|
|
@@ -38717,9 +39324,10 @@ Each domain tool has an 'action' parameter for specific operations.` : "";
|
|
|
38717
39324
|
};
|
|
38718
39325
|
}
|
|
38719
39326
|
case "version": {
|
|
38720
|
-
const result =
|
|
39327
|
+
const result = await loadMcpVersionInfo(client);
|
|
38721
39328
|
return {
|
|
38722
|
-
content: [{ type: "text", text: formatContent(result) }]
|
|
39329
|
+
content: [{ type: "text", text: formatContent(result) }],
|
|
39330
|
+
structuredContent: result
|
|
38723
39331
|
};
|
|
38724
39332
|
}
|
|
38725
39333
|
case "editor_rules": {
|
|
@@ -41093,6 +41701,8 @@ Environment variables:
|
|
|
41093
41701
|
CONTEXTSTREAM_SEARCH_MAX_CHARS Max chars per search result content (default: 400)
|
|
41094
41702
|
CONTEXTSTREAM_CONSOLIDATED Consolidated domain tools: true|false (default: true in v0.4.x, ~75% token reduction)
|
|
41095
41703
|
CONTEXTSTREAM_CONTEXT_PACK Enable Context Pack in context_smart: true|false (default: true)
|
|
41704
|
+
CONTEXTSTREAM_ALLOW_BROAD_INGEST Allow filesystem-root/home indexing only when explicitly set to 1
|
|
41705
|
+
CONTEXTSTREAM_ALLOW_API_PATH_INGEST Allow a non-loopback API to resolve local paths only when explicitly set to 1
|
|
41096
41706
|
CONTEXTSTREAM_PRO_TOOLS Optional comma-separated PRO tool names (default: AI tools)
|
|
41097
41707
|
CONTEXTSTREAM_UPGRADE_URL Optional upgrade URL shown for PRO tools on Free plan
|
|
41098
41708
|
CONTEXTSTREAM_ENABLE_PROMPTS Enable MCP prompts list (default: true)
|