@coreyuan/vector-mind 1.0.30 → 1.0.35
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 +2 -2
- package/dist/builtin-conventions.js +125 -0
- package/dist/builtin-conventions.js.map +1 -1
- package/dist/builtin-instructions.d.ts +3 -0
- package/dist/builtin-instructions.js +67 -0
- package/dist/builtin-instructions.js.map +1 -1
- package/dist/index.js +523 -156
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import fs from "node:fs";
|
|
|
4
4
|
import * as readline from "node:readline";
|
|
5
5
|
import crypto from "node:crypto";
|
|
6
6
|
import os from "node:os";
|
|
7
|
+
import { spawnSync } from "node:child_process";
|
|
7
8
|
import { fileURLToPath } from "node:url";
|
|
8
9
|
import chokidar from "chokidar";
|
|
9
10
|
import Database from "better-sqlite3";
|
|
@@ -13,9 +14,9 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
13
14
|
import { toJsonSchemaCompat } from "@modelcontextprotocol/sdk/server/zod-json-schema-compat.js";
|
|
14
15
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
15
16
|
import { BUILTIN_CONVENTIONS } from "./builtin-conventions.js";
|
|
16
|
-
import { BUILTIN_ARCHITECTURE_AND_CODE_ORGANIZATION_INSTRUCTIONS, BUILTIN_DESTRUCTIVE_OPERATION_GUARD_INSTRUCTIONS, BUILTIN_PLAN_LITE_INSTRUCTIONS, BUILTIN_WRITE_POLICY_INSTRUCTIONS, } from "./builtin-instructions.js";
|
|
17
|
+
import { BUILTIN_ARCHITECTURE_AND_CODE_ORGANIZATION_INSTRUCTIONS, BUILTIN_DESTRUCTIVE_OPERATION_GUARD_INSTRUCTIONS, BUILTIN_LOW_OVERHEAD_WORKFLOW_INSTRUCTIONS, BUILTIN_PAYLOAD_GUARD_INSTRUCTIONS, BUILTIN_PLAN_LITE_INSTRUCTIONS, BUILTIN_THREAD_HANDOFF_SWITCH_INSTRUCTIONS, BUILTIN_WRITE_POLICY_INSTRUCTIONS, } from "./builtin-instructions.js";
|
|
17
18
|
const SERVER_NAME = "vector-mind";
|
|
18
|
-
const SERVER_VERSION = "1.0.
|
|
19
|
+
const SERVER_VERSION = "1.0.35";
|
|
19
20
|
const rootFromEnv = process.env.VECTORMIND_ROOT?.trim() ?? "";
|
|
20
21
|
const prettyJsonOutput = ["1", "true", "on", "yes"].includes((process.env.VECTORMIND_PRETTY_JSON ?? "").trim().toLowerCase());
|
|
21
22
|
const debugLogEnabled = ["1", "true", "on", "yes"].includes((process.env.VECTORMIND_DEBUG_LOG ?? "").trim().toLowerCase());
|
|
@@ -64,6 +65,11 @@ const PENDING_PRUNE_EVERY = (() => {
|
|
|
64
65
|
return 500;
|
|
65
66
|
return n;
|
|
66
67
|
})();
|
|
68
|
+
const RIPGREP_RESOLVE_TIMEOUT_MS = 5_000;
|
|
69
|
+
const RIPGREP_SEARCH_TIMEOUT_MS = 30_000;
|
|
70
|
+
const RIPGREP_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
71
|
+
let cachedRipgrepCommand;
|
|
72
|
+
let cachedRipgrepResolveError = null;
|
|
67
73
|
function getCodexHomeDir() {
|
|
68
74
|
const raw = process.env.CODEX_HOME?.trim();
|
|
69
75
|
if (raw)
|
|
@@ -253,7 +259,7 @@ function summarizeActivityEvent(e) {
|
|
|
253
259
|
case "semantic_search":
|
|
254
260
|
return `semantic_search mode=${String(d.mode ?? "")} q=${String(d.query ?? "")} matches=${String(d.matches ?? "")}`;
|
|
255
261
|
case "grep":
|
|
256
|
-
return `grep q=${String(d.query ?? "")} matches=${String(d.matches ?? "")} truncated=${String(d.truncated ?? "")}`;
|
|
262
|
+
return `grep backend=${String(d.backend ?? "")} q=${String(d.query ?? "")} matches=${String(d.matches ?? "")} truncated=${String(d.truncated ?? "")}`;
|
|
257
263
|
case "query_codebase":
|
|
258
264
|
return `query_codebase q=${String(d.query ?? "")} matches=${String(d.matches ?? "")}`;
|
|
259
265
|
case "read_file_lines":
|
|
@@ -505,6 +511,22 @@ const IGNORED_PATH_SEGMENTS = new Set([
|
|
|
505
511
|
"x64",
|
|
506
512
|
"x86",
|
|
507
513
|
].map((s) => s.toLowerCase()));
|
|
514
|
+
const NOISE_FILE_SUFFIXES = [
|
|
515
|
+
".min.js",
|
|
516
|
+
".min.css",
|
|
517
|
+
".bundle.js",
|
|
518
|
+
".bundle.css",
|
|
519
|
+
".chunk.js",
|
|
520
|
+
".chunk.css",
|
|
521
|
+
];
|
|
522
|
+
const NOISE_FILE_BASENAMES = [
|
|
523
|
+
"package-lock.json",
|
|
524
|
+
"pnpm-lock.yaml",
|
|
525
|
+
"yarn.lock",
|
|
526
|
+
"bun.lockb",
|
|
527
|
+
"cargo.lock",
|
|
528
|
+
"composer.lock",
|
|
529
|
+
];
|
|
508
530
|
const IGNORED_LIKE_PATTERNS = (() => {
|
|
509
531
|
const patterns = [];
|
|
510
532
|
for (const seg of IGNORED_PATH_SEGMENTS) {
|
|
@@ -608,27 +630,11 @@ function pruneIgnoredIndexesByPathPatterns() {
|
|
|
608
630
|
function pruneFilenameNoiseIndexes() {
|
|
609
631
|
if (!db)
|
|
610
632
|
return { chunks_deleted: 0, symbols_deleted: 0 };
|
|
611
|
-
const suffixes = [
|
|
612
|
-
".min.js",
|
|
613
|
-
".min.css",
|
|
614
|
-
".bundle.js",
|
|
615
|
-
".bundle.css",
|
|
616
|
-
".chunk.js",
|
|
617
|
-
".chunk.css",
|
|
618
|
-
];
|
|
619
|
-
const baseNames = [
|
|
620
|
-
"package-lock.json",
|
|
621
|
-
"pnpm-lock.yaml",
|
|
622
|
-
"yarn.lock",
|
|
623
|
-
"bun.lockb",
|
|
624
|
-
"cargo.lock",
|
|
625
|
-
"composer.lock",
|
|
626
|
-
];
|
|
627
633
|
try {
|
|
628
|
-
const suffixWhere =
|
|
629
|
-
const baseWhere =
|
|
630
|
-
const suffixArgs =
|
|
631
|
-
const baseArgs =
|
|
634
|
+
const suffixWhere = NOISE_FILE_SUFFIXES.map(() => "LOWER(file_path) LIKE ?").join(" OR ");
|
|
635
|
+
const baseWhere = NOISE_FILE_BASENAMES.map(() => "LOWER(file_path) LIKE ?").join(" OR ");
|
|
636
|
+
const suffixArgs = NOISE_FILE_SUFFIXES.map((s) => `%${s}`);
|
|
637
|
+
const baseArgs = NOISE_FILE_BASENAMES.map((n) => `%/${n}`);
|
|
632
638
|
const whereParts = [];
|
|
633
639
|
const args = [];
|
|
634
640
|
if (suffixWhere) {
|
|
@@ -710,21 +716,9 @@ function isSymbolIndexableFile(filePath) {
|
|
|
710
716
|
}
|
|
711
717
|
function shouldIgnoreContentFile(filePath) {
|
|
712
718
|
const base = path.basename(filePath).toLowerCase();
|
|
713
|
-
|
|
714
|
-
"package-lock.json",
|
|
715
|
-
"pnpm-lock.yaml",
|
|
716
|
-
"yarn.lock",
|
|
717
|
-
"bun.lockb",
|
|
718
|
-
"cargo.lock",
|
|
719
|
-
"composer.lock",
|
|
720
|
-
]);
|
|
721
|
-
if (ignoreNames.has(base))
|
|
722
|
-
return true;
|
|
723
|
-
if (base.endsWith(".min.js") || base.endsWith(".min.css"))
|
|
724
|
-
return true;
|
|
725
|
-
if (base.endsWith(".bundle.js") || base.endsWith(".bundle.css"))
|
|
719
|
+
if (NOISE_FILE_BASENAMES.includes(base))
|
|
726
720
|
return true;
|
|
727
|
-
if (
|
|
721
|
+
if (NOISE_FILE_SUFFIXES.some((suffix) => base.endsWith(suffix)))
|
|
728
722
|
return true;
|
|
729
723
|
return false;
|
|
730
724
|
}
|
|
@@ -1175,13 +1169,14 @@ const GrepArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
|
1175
1169
|
// If case_sensitive is omitted and smart_case=true, uppercase => case-sensitive, otherwise case-insensitive.
|
|
1176
1170
|
smart_case: z.boolean().optional().default(true),
|
|
1177
1171
|
case_sensitive: z.boolean().optional(),
|
|
1178
|
-
//
|
|
1172
|
+
// Compatibility knob for the indexed fallback when ripgrep is unavailable.
|
|
1179
1173
|
literal_hint: z.string().optional().default(""),
|
|
1180
|
-
//
|
|
1174
|
+
// Compatibility knob for the indexed fallback when ripgrep is unavailable.
|
|
1181
1175
|
kinds: z.array(z.string().min(1)).optional(),
|
|
1182
1176
|
include_paths: z.array(z.string().min(1)).optional(),
|
|
1183
1177
|
exclude_paths: z.array(z.string().min(1)).optional(),
|
|
1184
1178
|
max_results: z.number().int().min(1).max(5000).optional().default(200),
|
|
1179
|
+
// Compatibility knob for the indexed fallback when ripgrep is unavailable.
|
|
1185
1180
|
max_candidates: z.number().int().min(1).max(50_000).optional(),
|
|
1186
1181
|
}));
|
|
1187
1182
|
const ReadFileLinesArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
@@ -1922,6 +1917,385 @@ function compileGrepRegex(opts) {
|
|
|
1922
1917
|
const source = opts.mode === "literal" ? escapeRegExp(opts.query) : opts.query;
|
|
1923
1918
|
return new RegExp(source, flags);
|
|
1924
1919
|
}
|
|
1920
|
+
function trimGrepText(input, maxChars) {
|
|
1921
|
+
if (input.length <= maxChars)
|
|
1922
|
+
return input;
|
|
1923
|
+
return `${input.slice(0, maxChars)}…`;
|
|
1924
|
+
}
|
|
1925
|
+
function buildGrepPreviewSnippet(lineText, col, maxChars = 500) {
|
|
1926
|
+
const clean = lineText.replace(/\r$/, "");
|
|
1927
|
+
if (clean.length <= maxChars)
|
|
1928
|
+
return clean;
|
|
1929
|
+
const matchIndex = Math.max(0, col - 1);
|
|
1930
|
+
let start = Math.max(0, matchIndex - Math.floor(maxChars * 0.35));
|
|
1931
|
+
if (start + maxChars > clean.length)
|
|
1932
|
+
start = Math.max(0, clean.length - maxChars);
|
|
1933
|
+
const end = Math.min(clean.length, start + maxChars);
|
|
1934
|
+
let snippet = clean.slice(start, end);
|
|
1935
|
+
if (start > 0)
|
|
1936
|
+
snippet = `…${snippet}`;
|
|
1937
|
+
if (end < clean.length)
|
|
1938
|
+
snippet = `${snippet}…`;
|
|
1939
|
+
return snippet;
|
|
1940
|
+
}
|
|
1941
|
+
function extractGrepMatchText(opts) {
|
|
1942
|
+
const clean = opts.lineText.replace(/\r$/, "");
|
|
1943
|
+
const startIndex = Math.max(0, opts.col - 1);
|
|
1944
|
+
if (opts.mode === "literal") {
|
|
1945
|
+
const slice = clean.slice(startIndex, startIndex + opts.query.length) || opts.query;
|
|
1946
|
+
return trimGrepText(slice, 200);
|
|
1947
|
+
}
|
|
1948
|
+
try {
|
|
1949
|
+
const flags = opts.caseSensitive ? "m" : "im";
|
|
1950
|
+
const anchored = new RegExp(opts.query, flags);
|
|
1951
|
+
const tail = clean.slice(startIndex);
|
|
1952
|
+
const found = anchored.exec(tail);
|
|
1953
|
+
if (found?.index === 0 && found[0])
|
|
1954
|
+
return trimGrepText(found[0], 200);
|
|
1955
|
+
}
|
|
1956
|
+
catch { }
|
|
1957
|
+
const fallback = clean.slice(startIndex, Math.min(clean.length, startIndex + 200));
|
|
1958
|
+
return trimGrepText(fallback || opts.query, 200);
|
|
1959
|
+
}
|
|
1960
|
+
function toProcessText(value) {
|
|
1961
|
+
if (typeof value === "string")
|
|
1962
|
+
return value;
|
|
1963
|
+
if (value == null)
|
|
1964
|
+
return "";
|
|
1965
|
+
return value.toString("utf8");
|
|
1966
|
+
}
|
|
1967
|
+
function formatProcessFailure(result) {
|
|
1968
|
+
if (result.error)
|
|
1969
|
+
return `${result.error.name}: ${result.error.message}`;
|
|
1970
|
+
const stderr = toProcessText(result.stderr).trim();
|
|
1971
|
+
if (stderr)
|
|
1972
|
+
return stderr;
|
|
1973
|
+
const stdout = toProcessText(result.stdout).trim();
|
|
1974
|
+
if (stdout)
|
|
1975
|
+
return stdout;
|
|
1976
|
+
if (typeof result.status === "number")
|
|
1977
|
+
return `exit ${result.status}`;
|
|
1978
|
+
if (result.signal)
|
|
1979
|
+
return `signal ${result.signal}`;
|
|
1980
|
+
return "unknown failure";
|
|
1981
|
+
}
|
|
1982
|
+
function buildRipgrepEnv() {
|
|
1983
|
+
const env = { ...process.env };
|
|
1984
|
+
delete env.RIPGREP_CONFIG_PATH;
|
|
1985
|
+
return env;
|
|
1986
|
+
}
|
|
1987
|
+
function pushUniqueCandidate(candidates, seen, raw) {
|
|
1988
|
+
const value = raw?.trim();
|
|
1989
|
+
if (!value || seen.has(value))
|
|
1990
|
+
return;
|
|
1991
|
+
seen.add(value);
|
|
1992
|
+
candidates.push(value);
|
|
1993
|
+
}
|
|
1994
|
+
function listChildDirsSafe(dirPath) {
|
|
1995
|
+
try {
|
|
1996
|
+
return fs
|
|
1997
|
+
.readdirSync(dirPath, { withFileTypes: true })
|
|
1998
|
+
.filter((entry) => entry.isDirectory())
|
|
1999
|
+
.map((entry) => path.join(dirPath, entry.name));
|
|
2000
|
+
}
|
|
2001
|
+
catch {
|
|
2002
|
+
return [];
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
function collectRipgrepCandidates() {
|
|
2006
|
+
const candidates = [];
|
|
2007
|
+
const seen = new Set();
|
|
2008
|
+
const override = process.env.VECTORMIND_RG_PATH?.trim();
|
|
2009
|
+
if (override)
|
|
2010
|
+
pushUniqueCandidate(candidates, seen, path.resolve(override));
|
|
2011
|
+
if (process.platform === "win32") {
|
|
2012
|
+
pushUniqueCandidate(candidates, seen, "rg.exe");
|
|
2013
|
+
pushUniqueCandidate(candidates, seen, "rg");
|
|
2014
|
+
}
|
|
2015
|
+
else {
|
|
2016
|
+
pushUniqueCandidate(candidates, seen, "rg");
|
|
2017
|
+
}
|
|
2018
|
+
for (const rawDir of (process.env.PATH ?? "").split(path.delimiter)) {
|
|
2019
|
+
const dir = rawDir.trim().replace(/^"+|"+$/g, "");
|
|
2020
|
+
if (!dir)
|
|
2021
|
+
continue;
|
|
2022
|
+
if (process.platform === "win32") {
|
|
2023
|
+
pushUniqueCandidate(candidates, seen, path.join(dir, "rg.exe"));
|
|
2024
|
+
pushUniqueCandidate(candidates, seen, path.join(dir, "rg"));
|
|
2025
|
+
}
|
|
2026
|
+
else {
|
|
2027
|
+
pushUniqueCandidate(candidates, seen, path.join(dir, "rg"));
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
if (process.platform === "win32") {
|
|
2031
|
+
const localAppData = process.env.LOCALAPPDATA?.trim();
|
|
2032
|
+
const programsDir = localAppData ? path.join(localAppData, "Programs") : "";
|
|
2033
|
+
if (programsDir && fs.existsSync(programsDir)) {
|
|
2034
|
+
for (const appDir of listChildDirsSafe(programsDir)) {
|
|
2035
|
+
pushUniqueCandidate(candidates, seen, path.join(appDir, "resources", "app", "node_modules", "@vscode", "ripgrep", "bin", "rg.exe"));
|
|
2036
|
+
pushUniqueCandidate(candidates, seen, path.join(appDir, "resources", "app", "extensions", "kiro.kiro-agent", "node_modules", "@vscode", "ripgrep", "bin", "rg.exe"));
|
|
2037
|
+
for (const childDir of listChildDirsSafe(appDir)) {
|
|
2038
|
+
pushUniqueCandidate(candidates, seen, path.join(childDir, "resources", "app", "node_modules", "@vscode", "ripgrep", "bin", "rg.exe"));
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
return candidates;
|
|
2044
|
+
}
|
|
2045
|
+
function resolveRipgrepCommand() {
|
|
2046
|
+
if (typeof cachedRipgrepCommand !== "undefined") {
|
|
2047
|
+
if (cachedRipgrepCommand)
|
|
2048
|
+
return { ok: true, command: cachedRipgrepCommand };
|
|
2049
|
+
return { ok: false, error: cachedRipgrepResolveError ?? "ripgrep unavailable", attempts: [] };
|
|
2050
|
+
}
|
|
2051
|
+
const env = buildRipgrepEnv();
|
|
2052
|
+
const attempts = [];
|
|
2053
|
+
for (const candidate of collectRipgrepCandidates()) {
|
|
2054
|
+
const probe = spawnSync(candidate, ["--version"], {
|
|
2055
|
+
cwd: projectRoot || undefined,
|
|
2056
|
+
env,
|
|
2057
|
+
encoding: "utf8",
|
|
2058
|
+
windowsHide: true,
|
|
2059
|
+
timeout: RIPGREP_RESOLVE_TIMEOUT_MS,
|
|
2060
|
+
maxBuffer: 256 * 1024,
|
|
2061
|
+
});
|
|
2062
|
+
if (probe.status === 0) {
|
|
2063
|
+
cachedRipgrepCommand = candidate;
|
|
2064
|
+
cachedRipgrepResolveError = null;
|
|
2065
|
+
return { ok: true, command: candidate };
|
|
2066
|
+
}
|
|
2067
|
+
attempts.push(`${candidate}: ${formatProcessFailure(probe)}`);
|
|
2068
|
+
}
|
|
2069
|
+
cachedRipgrepCommand = null;
|
|
2070
|
+
cachedRipgrepResolveError = attempts.slice(0, 8).join(" | ") || "ripgrep unavailable";
|
|
2071
|
+
return { ok: false, error: cachedRipgrepResolveError, attempts };
|
|
2072
|
+
}
|
|
2073
|
+
function appendBuiltInRipgrepExcludes(args) {
|
|
2074
|
+
for (const segment of IGNORED_PATH_SEGMENTS) {
|
|
2075
|
+
args.push("-g", `!${segment}/**`);
|
|
2076
|
+
args.push("-g", `!**/${segment}/**`);
|
|
2077
|
+
}
|
|
2078
|
+
for (const baseName of NOISE_FILE_BASENAMES) {
|
|
2079
|
+
args.push("-g", `!**/${baseName}`);
|
|
2080
|
+
}
|
|
2081
|
+
for (const suffix of NOISE_FILE_SUFFIXES) {
|
|
2082
|
+
args.push("-g", `!**/*${suffix}`);
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
function runRipgrepSearch(opts) {
|
|
2086
|
+
const resolved = resolveRipgrepCommand();
|
|
2087
|
+
if (!resolved.ok) {
|
|
2088
|
+
return { ok: false, unavailable: true, error: resolved.error, attempts: resolved.attempts };
|
|
2089
|
+
}
|
|
2090
|
+
const args = ["--vimgrep", "--no-heading", "--color", "never", "-m", String(opts.maxResults)];
|
|
2091
|
+
args.push(opts.caseSensitive ? "-s" : "-i");
|
|
2092
|
+
if (opts.mode === "literal")
|
|
2093
|
+
args.push("-F");
|
|
2094
|
+
appendBuiltInRipgrepExcludes(args);
|
|
2095
|
+
args.push("--", opts.query, ".");
|
|
2096
|
+
const result = spawnSync(resolved.command, args, {
|
|
2097
|
+
cwd: projectRoot,
|
|
2098
|
+
env: buildRipgrepEnv(),
|
|
2099
|
+
encoding: "utf8",
|
|
2100
|
+
windowsHide: true,
|
|
2101
|
+
timeout: RIPGREP_SEARCH_TIMEOUT_MS,
|
|
2102
|
+
maxBuffer: RIPGREP_MAX_BUFFER_BYTES,
|
|
2103
|
+
});
|
|
2104
|
+
if (result.error) {
|
|
2105
|
+
return {
|
|
2106
|
+
ok: false,
|
|
2107
|
+
unavailable: false,
|
|
2108
|
+
error: formatProcessFailure(result),
|
|
2109
|
+
attempts: [],
|
|
2110
|
+
rg_command: resolved.command,
|
|
2111
|
+
exit_status: result.status,
|
|
2112
|
+
};
|
|
2113
|
+
}
|
|
2114
|
+
const status = result.status ?? 0;
|
|
2115
|
+
if (status !== 0 && status !== 1) {
|
|
2116
|
+
return {
|
|
2117
|
+
ok: false,
|
|
2118
|
+
unavailable: false,
|
|
2119
|
+
error: formatProcessFailure(result),
|
|
2120
|
+
attempts: [],
|
|
2121
|
+
rg_command: resolved.command,
|
|
2122
|
+
exit_status: status,
|
|
2123
|
+
};
|
|
2124
|
+
}
|
|
2125
|
+
const matches = [];
|
|
2126
|
+
let totalMatches = 0;
|
|
2127
|
+
let truncated = false;
|
|
2128
|
+
for (const rawLine of toProcessText(result.stdout).split(/\r?\n/)) {
|
|
2129
|
+
if (!rawLine)
|
|
2130
|
+
continue;
|
|
2131
|
+
const parsed = /^(.*?):(\d+):(\d+):(.*)$/.exec(rawLine);
|
|
2132
|
+
if (!parsed)
|
|
2133
|
+
continue;
|
|
2134
|
+
const filePath = path.posix
|
|
2135
|
+
.normalize(parsed[1].replace(/\\/g, "/"))
|
|
2136
|
+
.replace(/^\.\/+/, "");
|
|
2137
|
+
const lineNumber = Number.parseInt(parsed[2] ?? "0", 10);
|
|
2138
|
+
const colNumber = Number.parseInt(parsed[3] ?? "0", 10);
|
|
2139
|
+
const lineText = (parsed[4] ?? "").replace(/\r$/, "");
|
|
2140
|
+
if (!filePath || !Number.isFinite(lineNumber) || !Number.isFinite(colNumber))
|
|
2141
|
+
continue;
|
|
2142
|
+
if (shouldIgnoreDbFilePath(filePath))
|
|
2143
|
+
continue;
|
|
2144
|
+
if (shouldIgnoreContentFile(filePath))
|
|
2145
|
+
continue;
|
|
2146
|
+
if (!passesPathFilters(filePath, opts.includePaths, opts.excludePaths))
|
|
2147
|
+
continue;
|
|
2148
|
+
totalMatches += 1;
|
|
2149
|
+
if (matches.length >= opts.maxResults) {
|
|
2150
|
+
truncated = true;
|
|
2151
|
+
continue;
|
|
2152
|
+
}
|
|
2153
|
+
matches.push({
|
|
2154
|
+
file_path: filePath,
|
|
2155
|
+
kind: "file_match",
|
|
2156
|
+
line: lineNumber,
|
|
2157
|
+
col: colNumber,
|
|
2158
|
+
preview: buildGrepPreviewSnippet(lineText, colNumber),
|
|
2159
|
+
match: extractGrepMatchText({
|
|
2160
|
+
lineText,
|
|
2161
|
+
query: opts.query,
|
|
2162
|
+
mode: opts.mode,
|
|
2163
|
+
caseSensitive: opts.caseSensitive,
|
|
2164
|
+
col: colNumber,
|
|
2165
|
+
}),
|
|
2166
|
+
});
|
|
2167
|
+
}
|
|
2168
|
+
return {
|
|
2169
|
+
ok: true,
|
|
2170
|
+
backend: "ripgrep",
|
|
2171
|
+
rg_command: resolved.command,
|
|
2172
|
+
matches,
|
|
2173
|
+
truncated,
|
|
2174
|
+
total_matches: totalMatches,
|
|
2175
|
+
};
|
|
2176
|
+
}
|
|
2177
|
+
function runIndexedGrepSearch(opts) {
|
|
2178
|
+
const hint = (() => {
|
|
2179
|
+
if (opts.mode === "literal")
|
|
2180
|
+
return opts.query;
|
|
2181
|
+
const explicit = opts.literalHint.trim();
|
|
2182
|
+
if (explicit)
|
|
2183
|
+
return explicit;
|
|
2184
|
+
return extractLongestLiteralFromRegex(opts.query);
|
|
2185
|
+
})();
|
|
2186
|
+
if (opts.mode === "regex" && hint.trim().length < 3) {
|
|
2187
|
+
throw new Error("Regex has no sufficiently long literal anchor for indexed narrowing. Provide literal_hint (>= 3 chars) or narrow with include_paths.");
|
|
2188
|
+
}
|
|
2189
|
+
let re;
|
|
2190
|
+
try {
|
|
2191
|
+
re = compileGrepRegex({
|
|
2192
|
+
query: opts.query,
|
|
2193
|
+
mode: opts.mode,
|
|
2194
|
+
caseSensitive: opts.caseSensitive,
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2197
|
+
catch (err) {
|
|
2198
|
+
throw new Error(`Invalid pattern: ${String(err)}`);
|
|
2199
|
+
}
|
|
2200
|
+
const maxCandidates = opts.maxCandidates ?? Math.min(50_000, Math.max(1000, opts.maxResults * 200));
|
|
2201
|
+
const candidates = (() => {
|
|
2202
|
+
if (ftsAvailable) {
|
|
2203
|
+
const matchQuery = buildFtsMatchQuery(hint);
|
|
2204
|
+
const placeholders = opts.kinds.map(() => "?").join(", ");
|
|
2205
|
+
const stmt = db.prepare(`
|
|
2206
|
+
SELECT
|
|
2207
|
+
m.id as id,
|
|
2208
|
+
m.kind as kind,
|
|
2209
|
+
m.content as content,
|
|
2210
|
+
m.file_path as file_path,
|
|
2211
|
+
m.start_line as start_line,
|
|
2212
|
+
m.end_line as end_line
|
|
2213
|
+
FROM ${FTS_TABLE_NAME}
|
|
2214
|
+
JOIN memory_items m ON m.id = ${FTS_TABLE_NAME}.rowid
|
|
2215
|
+
WHERE ${FTS_TABLE_NAME} MATCH ?
|
|
2216
|
+
AND m.kind IN (${placeholders})
|
|
2217
|
+
ORDER BY m.file_path ASC, m.start_line ASC, m.id ASC
|
|
2218
|
+
LIMIT ?
|
|
2219
|
+
`);
|
|
2220
|
+
return stmt.all(matchQuery, ...opts.kinds, maxCandidates);
|
|
2221
|
+
}
|
|
2222
|
+
const needle = opts.mode === "literal" ? opts.query : hint;
|
|
2223
|
+
const escaped = escapeLike(needle);
|
|
2224
|
+
const like = `%${escaped}%`;
|
|
2225
|
+
const placeholders = opts.kinds.map(() => "?").join(", ");
|
|
2226
|
+
const stmt = db.prepare(`
|
|
2227
|
+
SELECT
|
|
2228
|
+
id,
|
|
2229
|
+
kind,
|
|
2230
|
+
content,
|
|
2231
|
+
file_path,
|
|
2232
|
+
start_line,
|
|
2233
|
+
end_line
|
|
2234
|
+
FROM memory_items
|
|
2235
|
+
WHERE content LIKE ? ESCAPE '\\'
|
|
2236
|
+
AND kind IN (${placeholders})
|
|
2237
|
+
ORDER BY file_path ASC, start_line ASC, id ASC
|
|
2238
|
+
LIMIT ?
|
|
2239
|
+
`);
|
|
2240
|
+
return stmt.all(like, ...opts.kinds, maxCandidates);
|
|
2241
|
+
})();
|
|
2242
|
+
const matches = [];
|
|
2243
|
+
let candidatesScanned = 0;
|
|
2244
|
+
let truncated = false;
|
|
2245
|
+
for (const c of candidates) {
|
|
2246
|
+
candidatesScanned += 1;
|
|
2247
|
+
if (!c.file_path || c.start_line == null)
|
|
2248
|
+
continue;
|
|
2249
|
+
if (shouldIgnoreDbFilePath(c.file_path))
|
|
2250
|
+
continue;
|
|
2251
|
+
if (!passesPathFilters(c.file_path, opts.includePaths, opts.excludePaths))
|
|
2252
|
+
continue;
|
|
2253
|
+
const content = c.content ?? "";
|
|
2254
|
+
const lineStarts = buildLineStarts(content);
|
|
2255
|
+
re.lastIndex = 0;
|
|
2256
|
+
let m;
|
|
2257
|
+
while ((m = re.exec(content)) !== null) {
|
|
2258
|
+
const idx = m.index ?? 0;
|
|
2259
|
+
const matched = m[0] ?? "";
|
|
2260
|
+
if (!matched) {
|
|
2261
|
+
if (re.lastIndex >= content.length)
|
|
2262
|
+
break;
|
|
2263
|
+
re.lastIndex += 1;
|
|
2264
|
+
continue;
|
|
2265
|
+
}
|
|
2266
|
+
const lineIdx = lineIndexForOffset(lineStarts, idx);
|
|
2267
|
+
const lineStart = lineStarts[lineIdx] ?? 0;
|
|
2268
|
+
const lineEnd = lineIdx + 1 < lineStarts.length
|
|
2269
|
+
? (lineStarts[lineIdx + 1] ?? content.length) - 1
|
|
2270
|
+
: content.length;
|
|
2271
|
+
const previewRaw = content.slice(lineStart, Math.max(lineStart, lineEnd));
|
|
2272
|
+
matches.push({
|
|
2273
|
+
file_path: c.file_path,
|
|
2274
|
+
kind: c.kind,
|
|
2275
|
+
line: c.start_line + lineIdx,
|
|
2276
|
+
col: idx - lineStart + 1,
|
|
2277
|
+
preview: trimGrepText(previewRaw, 500),
|
|
2278
|
+
match: trimGrepText(matched, 200),
|
|
2279
|
+
});
|
|
2280
|
+
if (matches.length >= opts.maxResults) {
|
|
2281
|
+
truncated = true;
|
|
2282
|
+
break;
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
if (truncated)
|
|
2286
|
+
break;
|
|
2287
|
+
}
|
|
2288
|
+
return {
|
|
2289
|
+
backend: "indexed_fallback",
|
|
2290
|
+
hint,
|
|
2291
|
+
kinds: opts.kinds,
|
|
2292
|
+
include_paths: opts.includePaths ?? [],
|
|
2293
|
+
exclude_paths: opts.excludePaths ?? [],
|
|
2294
|
+
candidates: { total: candidates.length, scanned: candidatesScanned },
|
|
2295
|
+
matches,
|
|
2296
|
+
truncated,
|
|
2297
|
+
};
|
|
2298
|
+
}
|
|
1925
2299
|
function resolveProjectPathUnderRoot(inputPath, opts = {}) {
|
|
1926
2300
|
const normalizedInput = inputPath.trim() || ".";
|
|
1927
2301
|
const abs = path.isAbsolute(normalizedInput) ? normalizedInput : path.join(projectRoot, normalizedInput);
|
|
@@ -2150,16 +2524,31 @@ function buildServerInstructions() {
|
|
|
2150
2524
|
"Built-in architecture and code-organization policy:",
|
|
2151
2525
|
BUILTIN_ARCHITECTURE_AND_CODE_ORGANIZATION_INSTRUCTIONS,
|
|
2152
2526
|
"",
|
|
2527
|
+
"Built-in low-overhead execution and heavy-thread policy:",
|
|
2528
|
+
BUILTIN_LOW_OVERHEAD_WORKFLOW_INSTRUCTIONS,
|
|
2529
|
+
"",
|
|
2530
|
+
"Built-in payload / oversized-thread guard policy:",
|
|
2531
|
+
BUILTIN_PAYLOAD_GUARD_INSTRUCTIONS,
|
|
2532
|
+
"",
|
|
2533
|
+
"Built-in thread handoff / switch-gate policy:",
|
|
2534
|
+
BUILTIN_THREAD_HANDOFF_SWITCH_INSTRUCTIONS,
|
|
2535
|
+
"",
|
|
2153
2536
|
"Required workflow:",
|
|
2154
|
-
"- On every new conversation/session: call bootstrap_context({ query: <current goal> }) first (or at least get_brain_dump()) to restore context and retrieve relevant matches from the local memory store (vector if enabled; otherwise FTS/LIKE).",
|
|
2537
|
+
"- On every new conversation/session for analysis/design/development work: call bootstrap_context({ query: <current goal> }) first (or at least get_brain_dump()) to restore context and retrieve relevant matches from the local memory store (vector if enabled; otherwise FTS/LIKE).",
|
|
2155
2538
|
" - Output is compact by default. Use include_content=true only when you truly need full text (it increases tokens).",
|
|
2156
2539
|
" - Tune output size with: requirements_limit/changes_limit/notes_limit, preview_chars, pending_limit/pending_offset.",
|
|
2157
2540
|
" - Prefer read_memory_item(id, offset, limit) to fetch full text on demand instead of returning large content in other tool outputs.",
|
|
2541
|
+
"- For pure execution-first tasks with explicit targets (for example compile/build/run/launch/package/publish/test rerun), you may skip retrieval and go straight to the minimum necessary shell or host tools unless code/context lookup is actually needed to unblock execution.",
|
|
2158
2542
|
"- To read local Codex skill/prompt/rule files (for example SKILL.md under CODEX_HOME or AGENTS_HOME), prefer read_codex_text_file({ path }) instead of assuming a filesystem MCP resource server exists.",
|
|
2159
2543
|
"- For project file/directory browsing, prefer list_project_files({ path, recursive?, max_depth? }) over shelling out to Get-ChildItem/ls. It respects ignore rules and keeps output bounded.",
|
|
2160
2544
|
"- For small/medium raw file reads, prefer read_file_text({ path, offset?, max_chars? }) over Get-Content -Raw. Use read_file_lines(...) when you need deterministic line ranges or the file may be large.",
|
|
2161
|
-
"- For
|
|
2545
|
+
"- For raw repo text search with exact file+line+col matches, prefer grep({ query: <pattern> }). It uses ripgrep against real project files when available, applies built-in noise filters, and only falls back to indexed search if ripgrep is unavailable.",
|
|
2162
2546
|
"- To read a bounded segment of a file, prefer read_file_lines({ path: <file>, from_line/to_line or total_count }) over unbounded file reads.",
|
|
2547
|
+
"- If the current thread is heavy, recently compacted, has become slow, or has already hit a 413 / Payload Too Large style error, switch to payload guard mode: avoid unbounded shell dumps, prefer bounded MCP tools, and summarize outputs instead of pasting large raw blocks.",
|
|
2548
|
+
"- In payload guard mode, do not use full-repo recursive listings, whole-file dumps, or broad raw match echo unless the user explicitly requests that raw output and accepts the size risk.",
|
|
2549
|
+
"- Thread-switch judgment must not rely on a fixed token threshold; use observable signals plus the weight of the upcoming work. If the current thread is heavy, repeatedly compacting, slow, or has already hit a 413 / Payload Too Large style error and the next work still needs broad analysis, cross-module investigation, release validation, or other substantial continuation, pause once and ask whether to switch to a fresh thread before continuing.",
|
|
2550
|
+
"- If the user declines that switch, continue in the current thread and do not raise the thread-switch reminder again in the same session.",
|
|
2551
|
+
"- If the user accepts and the host cannot create a new top-level thread automatically, output the fixed THREAD_HANDOFF_PACK template plus the fixed copy-ready first message for the new thread instead of dumping raw history or the last N chat turns.",
|
|
2163
2552
|
"- BEFORE editing code: call start_requirement(title, background) to set the active requirement.",
|
|
2164
2553
|
"- AFTER editing + saving: call get_pending_changes() to see unsynced files, then call sync_change_intent(intent, files). (You can omit files to auto-link all pending changes.)",
|
|
2165
2554
|
"- After major milestones/decisions: call upsert_project_summary(summary) and/or add_note(...) to persist durable context locally.",
|
|
@@ -2167,6 +2556,7 @@ function buildServerInstructions() {
|
|
|
2167
2556
|
"- When you need full text for a specific note/summary/match: call read_memory_item(id, offset, limit) and page through it.",
|
|
2168
2557
|
"- When asked to locate code (class/function/type): call query_codebase(query) instead of guessing.",
|
|
2169
2558
|
"- When you need to recall relevant context from history/code/docs: call semantic_search(query, ...) instead of guessing.",
|
|
2559
|
+
"- If the current thread is already heavy or the user reports it has become slow, switch to a lighter workflow: avoid redundant retrieval, keep outputs compact, and if the user refuses thread switching, continue in light mode without repeating the switch reminder in that same session.",
|
|
2170
2560
|
"",
|
|
2171
2561
|
"If tool output conflicts with assumptions, trust the tool output.",
|
|
2172
2562
|
].join("\n");
|
|
@@ -2732,7 +3122,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
2732
3122
|
},
|
|
2733
3123
|
{
|
|
2734
3124
|
name: "grep",
|
|
2735
|
-
description: "
|
|
3125
|
+
description: "Repo text search with precise file/line/col matches, powered by ripgrep against real project files plus built-in noise filters. Falls back to indexed search only when ripgrep is unavailable.",
|
|
2736
3126
|
inputSchema: toJsonSchemaCompat(GrepArgsSchema),
|
|
2737
3127
|
},
|
|
2738
3128
|
{
|
|
@@ -3434,15 +3824,53 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3434
3824
|
const includePaths = args.include_paths?.length ? args.include_paths : null;
|
|
3435
3825
|
const excludePaths = args.exclude_paths?.length ? args.exclude_paths : null;
|
|
3436
3826
|
const maxResults = args.max_results;
|
|
3437
|
-
const
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3827
|
+
const caseSensitive = args.case_sensitive ?? (smartCase ? hasUppercaseAscii(q) : true);
|
|
3828
|
+
const ripgrepResult = runRipgrepSearch({
|
|
3829
|
+
query: q,
|
|
3830
|
+
mode,
|
|
3831
|
+
smartCase,
|
|
3832
|
+
caseSensitive,
|
|
3833
|
+
includePaths,
|
|
3834
|
+
excludePaths,
|
|
3835
|
+
maxResults,
|
|
3836
|
+
});
|
|
3837
|
+
if (ripgrepResult.ok) {
|
|
3838
|
+
logActivity("grep", {
|
|
3839
|
+
backend: ripgrepResult.backend,
|
|
3840
|
+
rg_command: ripgrepResult.rg_command,
|
|
3841
|
+
query: q,
|
|
3842
|
+
mode,
|
|
3843
|
+
case_sensitive: caseSensitive,
|
|
3844
|
+
smart_case: smartCase,
|
|
3845
|
+
include_paths: includePaths ?? [],
|
|
3846
|
+
exclude_paths: excludePaths ?? [],
|
|
3847
|
+
matches: ripgrepResult.matches.length,
|
|
3848
|
+
total_matches: ripgrepResult.total_matches,
|
|
3849
|
+
truncated: ripgrepResult.truncated,
|
|
3850
|
+
});
|
|
3851
|
+
return {
|
|
3852
|
+
content: [
|
|
3853
|
+
{
|
|
3854
|
+
type: "text",
|
|
3855
|
+
text: toolJson({
|
|
3856
|
+
ok: true,
|
|
3857
|
+
backend: ripgrepResult.backend,
|
|
3858
|
+
rg_command: ripgrepResult.rg_command,
|
|
3859
|
+
query: q,
|
|
3860
|
+
mode,
|
|
3861
|
+
case_sensitive: caseSensitive,
|
|
3862
|
+
smart_case: smartCase,
|
|
3863
|
+
include_paths: includePaths ?? [],
|
|
3864
|
+
exclude_paths: excludePaths ?? [],
|
|
3865
|
+
matches: ripgrepResult.matches,
|
|
3866
|
+
total_matches: ripgrepResult.total_matches,
|
|
3867
|
+
truncated: ripgrepResult.truncated,
|
|
3868
|
+
}),
|
|
3869
|
+
},
|
|
3870
|
+
],
|
|
3871
|
+
};
|
|
3872
|
+
}
|
|
3873
|
+
if (!ripgrepResult.unavailable) {
|
|
3446
3874
|
return {
|
|
3447
3875
|
isError: true,
|
|
3448
3876
|
content: [
|
|
@@ -3450,19 +3878,31 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3450
3878
|
type: "text",
|
|
3451
3879
|
text: toolJson({
|
|
3452
3880
|
ok: false,
|
|
3453
|
-
|
|
3881
|
+
backend: "ripgrep",
|
|
3882
|
+
error: ripgrepResult.error,
|
|
3883
|
+
rg_command: ripgrepResult.rg_command,
|
|
3884
|
+
exit_status: ripgrepResult.exit_status,
|
|
3454
3885
|
query: q,
|
|
3455
3886
|
mode,
|
|
3456
|
-
literal_hint: args.literal_hint,
|
|
3457
3887
|
}),
|
|
3458
3888
|
},
|
|
3459
3889
|
],
|
|
3460
3890
|
};
|
|
3461
3891
|
}
|
|
3462
|
-
|
|
3463
|
-
let re;
|
|
3892
|
+
let indexedResult;
|
|
3464
3893
|
try {
|
|
3465
|
-
|
|
3894
|
+
indexedResult = runIndexedGrepSearch({
|
|
3895
|
+
query: q,
|
|
3896
|
+
mode,
|
|
3897
|
+
smartCase,
|
|
3898
|
+
caseSensitive,
|
|
3899
|
+
literalHint: args.literal_hint,
|
|
3900
|
+
kinds,
|
|
3901
|
+
includePaths,
|
|
3902
|
+
excludePaths,
|
|
3903
|
+
maxResults,
|
|
3904
|
+
maxCandidates: args.max_candidates,
|
|
3905
|
+
});
|
|
3466
3906
|
}
|
|
3467
3907
|
catch (err) {
|
|
3468
3908
|
return {
|
|
@@ -3470,114 +3910,37 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3470
3910
|
content: [
|
|
3471
3911
|
{
|
|
3472
3912
|
type: "text",
|
|
3473
|
-
text: toolJson({
|
|
3913
|
+
text: toolJson({
|
|
3914
|
+
ok: false,
|
|
3915
|
+
backend: "indexed_fallback",
|
|
3916
|
+
fallback_reason: "ripgrep_unavailable",
|
|
3917
|
+
ripgrep_error: ripgrepResult.error,
|
|
3918
|
+
ripgrep_attempts: ripgrepResult.attempts,
|
|
3919
|
+
error: String(err),
|
|
3920
|
+
query: q,
|
|
3921
|
+
mode,
|
|
3922
|
+
literal_hint: args.literal_hint,
|
|
3923
|
+
}),
|
|
3474
3924
|
},
|
|
3475
3925
|
],
|
|
3476
3926
|
};
|
|
3477
3927
|
}
|
|
3478
|
-
const maxCandidates = args.max_candidates ?? Math.min(50_000, Math.max(1000, maxResults * 200));
|
|
3479
|
-
const candidates = (() => {
|
|
3480
|
-
if (ftsAvailable) {
|
|
3481
|
-
const matchQuery = buildFtsMatchQuery(hint);
|
|
3482
|
-
const placeholders = kinds.map(() => "?").join(", ");
|
|
3483
|
-
const stmt = db.prepare(`
|
|
3484
|
-
SELECT
|
|
3485
|
-
m.id as id,
|
|
3486
|
-
m.kind as kind,
|
|
3487
|
-
m.content as content,
|
|
3488
|
-
m.file_path as file_path,
|
|
3489
|
-
m.start_line as start_line,
|
|
3490
|
-
m.end_line as end_line
|
|
3491
|
-
FROM ${FTS_TABLE_NAME}
|
|
3492
|
-
JOIN memory_items m ON m.id = ${FTS_TABLE_NAME}.rowid
|
|
3493
|
-
WHERE ${FTS_TABLE_NAME} MATCH ?
|
|
3494
|
-
AND m.kind IN (${placeholders})
|
|
3495
|
-
ORDER BY m.file_path ASC, m.start_line ASC, m.id ASC
|
|
3496
|
-
LIMIT ?
|
|
3497
|
-
`);
|
|
3498
|
-
return stmt.all(matchQuery, ...kinds, maxCandidates);
|
|
3499
|
-
}
|
|
3500
|
-
const needle = mode === "literal" ? q : hint;
|
|
3501
|
-
const escaped = escapeLike(needle);
|
|
3502
|
-
const like = `%${escaped}%`;
|
|
3503
|
-
const placeholders = kinds.map(() => "?").join(", ");
|
|
3504
|
-
const stmt = db.prepare(`
|
|
3505
|
-
SELECT
|
|
3506
|
-
id,
|
|
3507
|
-
kind,
|
|
3508
|
-
content,
|
|
3509
|
-
file_path,
|
|
3510
|
-
start_line,
|
|
3511
|
-
end_line
|
|
3512
|
-
FROM memory_items
|
|
3513
|
-
WHERE content LIKE ? ESCAPE '\\'
|
|
3514
|
-
AND kind IN (${placeholders})
|
|
3515
|
-
ORDER BY file_path ASC, start_line ASC, id ASC
|
|
3516
|
-
LIMIT ?
|
|
3517
|
-
`);
|
|
3518
|
-
return stmt.all(like, ...kinds, maxCandidates);
|
|
3519
|
-
})();
|
|
3520
|
-
const matches = [];
|
|
3521
|
-
let candidatesScanned = 0;
|
|
3522
|
-
let truncated = false;
|
|
3523
|
-
for (const c of candidates) {
|
|
3524
|
-
candidatesScanned += 1;
|
|
3525
|
-
if (!c.file_path || c.start_line == null)
|
|
3526
|
-
continue;
|
|
3527
|
-
if (shouldIgnoreDbFilePath(c.file_path))
|
|
3528
|
-
continue;
|
|
3529
|
-
if (!passesPathFilters(c.file_path, includePaths, excludePaths))
|
|
3530
|
-
continue;
|
|
3531
|
-
const content = c.content ?? "";
|
|
3532
|
-
const lineStarts = buildLineStarts(content);
|
|
3533
|
-
re.lastIndex = 0;
|
|
3534
|
-
let m;
|
|
3535
|
-
while ((m = re.exec(content)) !== null) {
|
|
3536
|
-
const idx = m.index ?? 0;
|
|
3537
|
-
const matched = m[0] ?? "";
|
|
3538
|
-
if (!matched) {
|
|
3539
|
-
if (re.lastIndex >= content.length)
|
|
3540
|
-
break;
|
|
3541
|
-
re.lastIndex += 1;
|
|
3542
|
-
continue;
|
|
3543
|
-
}
|
|
3544
|
-
const lineIdx = lineIndexForOffset(lineStarts, idx);
|
|
3545
|
-
const lineStart = lineStarts[lineIdx] ?? 0;
|
|
3546
|
-
const lineEnd = lineIdx + 1 < lineStarts.length
|
|
3547
|
-
? (lineStarts[lineIdx + 1] ?? content.length) - 1
|
|
3548
|
-
: content.length;
|
|
3549
|
-
const previewRaw = content.slice(lineStart, Math.max(lineStart, lineEnd));
|
|
3550
|
-
const preview = previewRaw.length > 500 ? `${previewRaw.slice(0, 500)}…` : previewRaw;
|
|
3551
|
-
const matchText = matched.length > 200 ? `${matched.slice(0, 200)}…` : matched;
|
|
3552
|
-
matches.push({
|
|
3553
|
-
file_path: c.file_path,
|
|
3554
|
-
kind: c.kind,
|
|
3555
|
-
line: c.start_line + lineIdx,
|
|
3556
|
-
col: idx - lineStart + 1,
|
|
3557
|
-
preview,
|
|
3558
|
-
match: matchText,
|
|
3559
|
-
});
|
|
3560
|
-
if (matches.length >= maxResults) {
|
|
3561
|
-
truncated = true;
|
|
3562
|
-
break;
|
|
3563
|
-
}
|
|
3564
|
-
}
|
|
3565
|
-
if (truncated)
|
|
3566
|
-
break;
|
|
3567
|
-
}
|
|
3568
3928
|
logActivity("grep", {
|
|
3929
|
+
backend: indexedResult.backend,
|
|
3930
|
+
fallback_reason: "ripgrep_unavailable",
|
|
3931
|
+
ripgrep_error: ripgrepResult.error,
|
|
3569
3932
|
query: q,
|
|
3570
3933
|
mode,
|
|
3571
3934
|
case_sensitive: caseSensitive,
|
|
3572
3935
|
smart_case: smartCase,
|
|
3573
|
-
hint,
|
|
3936
|
+
hint: indexedResult.hint,
|
|
3574
3937
|
kinds,
|
|
3575
3938
|
include_paths: includePaths ?? [],
|
|
3576
3939
|
exclude_paths: excludePaths ?? [],
|
|
3577
|
-
candidates: candidates.
|
|
3578
|
-
candidates_scanned:
|
|
3579
|
-
matches: matches.length,
|
|
3580
|
-
truncated,
|
|
3940
|
+
candidates: indexedResult.candidates.total,
|
|
3941
|
+
candidates_scanned: indexedResult.candidates.scanned,
|
|
3942
|
+
matches: indexedResult.matches.length,
|
|
3943
|
+
truncated: indexedResult.truncated,
|
|
3581
3944
|
});
|
|
3582
3945
|
return {
|
|
3583
3946
|
content: [
|
|
@@ -3585,17 +3948,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3585
3948
|
type: "text",
|
|
3586
3949
|
text: toolJson({
|
|
3587
3950
|
ok: true,
|
|
3951
|
+
backend: indexedResult.backend,
|
|
3952
|
+
fallback_reason: "ripgrep_unavailable",
|
|
3953
|
+
ripgrep_error: ripgrepResult.error,
|
|
3954
|
+
ripgrep_attempts: ripgrepResult.attempts,
|
|
3588
3955
|
query: q,
|
|
3589
3956
|
mode,
|
|
3590
3957
|
case_sensitive: caseSensitive,
|
|
3591
3958
|
smart_case: smartCase,
|
|
3592
|
-
hint,
|
|
3959
|
+
hint: indexedResult.hint,
|
|
3593
3960
|
kinds,
|
|
3594
3961
|
include_paths: includePaths ?? [],
|
|
3595
3962
|
exclude_paths: excludePaths ?? [],
|
|
3596
|
-
candidates:
|
|
3597
|
-
matches,
|
|
3598
|
-
truncated,
|
|
3963
|
+
candidates: indexedResult.candidates,
|
|
3964
|
+
matches: indexedResult.matches,
|
|
3965
|
+
truncated: indexedResult.truncated,
|
|
3599
3966
|
}),
|
|
3600
3967
|
},
|
|
3601
3968
|
],
|