@usewhisper/mcp-server 2.9.0 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server.js +629 -252
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -6,7 +6,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { execSync, spawnSync } from "child_process";
|
|
8
8
|
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync, appendFileSync } from "fs";
|
|
9
|
-
import { join, relative, extname } from "path";
|
|
9
|
+
import { join, relative, extname, normalize as normalizePath, resolve as resolvePath } from "path";
|
|
10
10
|
import { homedir } from "os";
|
|
11
11
|
import { createHash, randomUUID } from "crypto";
|
|
12
12
|
|
|
@@ -3879,15 +3879,50 @@ function ensureStateDir() {
|
|
|
3879
3879
|
mkdirSync(STATE_DIR, { recursive: true });
|
|
3880
3880
|
}
|
|
3881
3881
|
}
|
|
3882
|
+
function canonicalizeWorkspacePath(path, cwd = process.cwd()) {
|
|
3883
|
+
const basePath = path?.trim() ? path.trim() : cwd;
|
|
3884
|
+
const absolute = resolvePath(cwd, basePath);
|
|
3885
|
+
const normalized = normalizePath(absolute).replace(/\\/g, "/");
|
|
3886
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
3887
|
+
}
|
|
3888
|
+
function chooseWorkspaceProjectSource(args) {
|
|
3889
|
+
const explicitProject = args.explicitProject?.trim();
|
|
3890
|
+
if (explicitProject) {
|
|
3891
|
+
return { project_ref: explicitProject, resolved_by: "explicit_project" };
|
|
3892
|
+
}
|
|
3893
|
+
const workspaceProjectRef = args.workspaceProjectRef?.trim();
|
|
3894
|
+
if (workspaceProjectRef) {
|
|
3895
|
+
return { project_ref: workspaceProjectRef, resolved_by: "workspace_binding" };
|
|
3896
|
+
}
|
|
3897
|
+
const defaultProject = args.defaultProject?.trim();
|
|
3898
|
+
if (defaultProject) {
|
|
3899
|
+
return { project_ref: defaultProject, resolved_by: "env_default" };
|
|
3900
|
+
}
|
|
3901
|
+
return { project_ref: null, resolved_by: "unresolved" };
|
|
3902
|
+
}
|
|
3903
|
+
function classifyWorkspaceHealth(args) {
|
|
3904
|
+
if (!args.project_ref) return "unbound";
|
|
3905
|
+
if (!args.last_indexed_at || (args.coverage ?? 0) <= 0) return "unindexed";
|
|
3906
|
+
const nowMs = args.now_ms ?? Date.now();
|
|
3907
|
+
const lastIndexedMs = new Date(args.last_indexed_at).getTime();
|
|
3908
|
+
const ageHours = Number.isFinite(lastIndexedMs) ? (nowMs - lastIndexedMs) / (60 * 60 * 1e3) : Number.POSITIVE_INFINITY;
|
|
3909
|
+
const pendingChanges = args.pending_changes ?? 0;
|
|
3910
|
+
const drifted = pendingChanges > 0 || Boolean(
|
|
3911
|
+
args.last_indexed_commit && args.current_commit && args.last_indexed_commit !== args.current_commit
|
|
3912
|
+
);
|
|
3913
|
+
if (drifted) return "drifted";
|
|
3914
|
+
if (ageHours > (args.max_staleness_hours ?? 168)) return "stale";
|
|
3915
|
+
return "healthy";
|
|
3916
|
+
}
|
|
3882
3917
|
function getWorkspaceId(workspaceId) {
|
|
3883
3918
|
if (workspaceId?.trim()) return workspaceId.trim();
|
|
3884
|
-
const seed = `${process.cwd()}|${
|
|
3919
|
+
const seed = `${canonicalizeWorkspacePath(process.cwd())}|${API_KEY.slice(0, 12) || "anon"}`;
|
|
3885
3920
|
return createHash("sha256").update(seed).digest("hex").slice(0, 20);
|
|
3886
3921
|
}
|
|
3887
3922
|
function getWorkspaceIdForPath(path, workspaceId) {
|
|
3888
3923
|
if (workspaceId?.trim()) return workspaceId.trim();
|
|
3889
3924
|
if (!path) return getWorkspaceId(void 0);
|
|
3890
|
-
const seed = `${path}|${
|
|
3925
|
+
const seed = `${canonicalizeWorkspacePath(path)}|${API_KEY.slice(0, 12) || "anon"}`;
|
|
3891
3926
|
return createHash("sha256").update(seed).digest("hex").slice(0, 20);
|
|
3892
3927
|
}
|
|
3893
3928
|
function clamp012(value) {
|
|
@@ -3978,7 +4013,10 @@ function getWorkspaceState(state, workspaceId) {
|
|
|
3978
4013
|
annotations: [],
|
|
3979
4014
|
session_summaries: [],
|
|
3980
4015
|
events: [],
|
|
3981
|
-
index_metadata: {}
|
|
4016
|
+
index_metadata: {},
|
|
4017
|
+
root_path: void 0,
|
|
4018
|
+
project_ref: void 0,
|
|
4019
|
+
project_id: void 0
|
|
3982
4020
|
};
|
|
3983
4021
|
}
|
|
3984
4022
|
return state.workspaces[workspaceId];
|
|
@@ -3988,17 +4026,124 @@ function computeChecksum(value) {
|
|
|
3988
4026
|
}
|
|
3989
4027
|
var cachedProjectRef = DEFAULT_PROJECT || void 0;
|
|
3990
4028
|
var cachedMcpSessionId = process.env.WHISPER_SESSION_ID || `mcp_${randomUUID().slice(0, 12)}`;
|
|
4029
|
+
async function resolveProjectDescriptor(projectRef) {
|
|
4030
|
+
try {
|
|
4031
|
+
const resolved = await whisper.resolveProject(projectRef);
|
|
4032
|
+
const resolvedRef = resolved.slug || resolved.name || resolved.id || projectRef;
|
|
4033
|
+
cachedProjectRef = resolvedRef;
|
|
4034
|
+
return { project_ref: resolvedRef, project_id: resolved.id || null };
|
|
4035
|
+
} catch {
|
|
4036
|
+
cachedProjectRef = projectRef;
|
|
4037
|
+
return { project_ref: projectRef, project_id: null };
|
|
4038
|
+
}
|
|
4039
|
+
}
|
|
4040
|
+
function getWorkspaceRecommendedNextCalls(health) {
|
|
4041
|
+
if (health === "unbound") return ["index.workspace_resolve", "context.list_projects", "index.workspace_status"];
|
|
4042
|
+
if (health === "unindexed") return ["index.workspace_status", "index.workspace_run", "search_code", "grep"];
|
|
4043
|
+
if (health === "stale" || health === "drifted") return ["index.workspace_status", "index.workspace_run", "grep", "search_code"];
|
|
4044
|
+
return [];
|
|
4045
|
+
}
|
|
4046
|
+
function getWorkspaceWarnings(args) {
|
|
4047
|
+
if (args.health === "unbound") {
|
|
4048
|
+
return [`Workspace ${args.rootPath} is not bound to a Whisper project.`];
|
|
4049
|
+
}
|
|
4050
|
+
if (args.health === "unindexed") {
|
|
4051
|
+
return [`Workspace ${args.rootPath} is bound to ${args.projectRef}, but no usable local index metadata exists yet.`];
|
|
4052
|
+
}
|
|
4053
|
+
if (args.health === "stale") {
|
|
4054
|
+
const age = args.freshness.age_hours == null ? "unknown" : `${Math.round(args.freshness.age_hours)}h`;
|
|
4055
|
+
return [`Workspace index for ${args.projectRef} is stale (${age} old).`];
|
|
4056
|
+
}
|
|
4057
|
+
if (args.health === "drifted") {
|
|
4058
|
+
const reasons = [];
|
|
4059
|
+
if (args.pendingChanges && args.pendingChanges > 0) reasons.push(`${args.pendingChanges} pending local changes`);
|
|
4060
|
+
if (args.lastIndexedCommit && args.currentCommit && args.lastIndexedCommit !== args.currentCommit) {
|
|
4061
|
+
reasons.push(`indexed commit ${args.lastIndexedCommit.slice(0, 12)} differs from current ${args.currentCommit.slice(0, 12)}`);
|
|
4062
|
+
}
|
|
4063
|
+
const suffix = reasons.length ? `: ${reasons.join("; ")}` : "";
|
|
4064
|
+
return [`Workspace state drifted since the last index${suffix}.`];
|
|
4065
|
+
}
|
|
4066
|
+
return [];
|
|
4067
|
+
}
|
|
4068
|
+
async function resolveWorkspaceTrust(args) {
|
|
4069
|
+
const rootPath = canonicalizeWorkspacePath(args.path);
|
|
4070
|
+
const workspaceId = getWorkspaceIdForPath(rootPath, args.workspace_id);
|
|
4071
|
+
const state = loadState();
|
|
4072
|
+
const workspace = getWorkspaceState(state, workspaceId);
|
|
4073
|
+
let mutated = false;
|
|
4074
|
+
if (workspace.root_path !== rootPath) {
|
|
4075
|
+
workspace.root_path = rootPath;
|
|
4076
|
+
mutated = true;
|
|
4077
|
+
}
|
|
4078
|
+
const selected = chooseWorkspaceProjectSource({
|
|
4079
|
+
explicitProject: args.project,
|
|
4080
|
+
workspaceProjectRef: workspace.project_ref,
|
|
4081
|
+
defaultProject: DEFAULT_PROJECT || null
|
|
4082
|
+
});
|
|
4083
|
+
let projectRef = null;
|
|
4084
|
+
let projectId = workspace.project_id || null;
|
|
4085
|
+
if (selected.project_ref) {
|
|
4086
|
+
const resolved = await resolveProjectDescriptor(selected.project_ref);
|
|
4087
|
+
projectRef = resolved.project_ref;
|
|
4088
|
+
projectId = resolved.project_id;
|
|
4089
|
+
if (workspace.project_ref !== projectRef || (workspace.project_id || null) !== projectId) {
|
|
4090
|
+
workspace.project_ref = projectRef;
|
|
4091
|
+
workspace.project_id = projectId || void 0;
|
|
4092
|
+
mutated = true;
|
|
4093
|
+
}
|
|
4094
|
+
}
|
|
4095
|
+
if (mutated) saveState(state);
|
|
4096
|
+
const lastIndexedAt = workspace.index_metadata?.last_indexed_at || null;
|
|
4097
|
+
const ageHours = lastIndexedAt ? (Date.now() - new Date(lastIndexedAt).getTime()) / (60 * 60 * 1e3) : null;
|
|
4098
|
+
const currentCommit = getGitHead(rootPath) || null;
|
|
4099
|
+
const pendingChanges = getGitPendingCount(rootPath);
|
|
4100
|
+
const lastIndexedCommit = workspace.index_metadata?.last_indexed_commit || null;
|
|
4101
|
+
const coverage = workspace.index_metadata?.coverage ?? 0;
|
|
4102
|
+
const health = classifyWorkspaceHealth({
|
|
4103
|
+
project_ref: projectRef,
|
|
4104
|
+
coverage,
|
|
4105
|
+
last_indexed_at: lastIndexedAt,
|
|
4106
|
+
last_indexed_commit: lastIndexedCommit,
|
|
4107
|
+
current_commit: currentCommit,
|
|
4108
|
+
pending_changes: pendingChanges ?? null,
|
|
4109
|
+
max_staleness_hours: args.max_staleness_hours ?? 168
|
|
4110
|
+
});
|
|
4111
|
+
const freshness = {
|
|
4112
|
+
stale: health === "stale",
|
|
4113
|
+
age_hours: ageHours,
|
|
4114
|
+
last_indexed_at: lastIndexedAt
|
|
4115
|
+
};
|
|
4116
|
+
const warnings = getWorkspaceWarnings({
|
|
4117
|
+
health,
|
|
4118
|
+
rootPath,
|
|
4119
|
+
projectRef,
|
|
4120
|
+
currentCommit,
|
|
4121
|
+
lastIndexedCommit,
|
|
4122
|
+
pendingChanges: pendingChanges ?? null,
|
|
4123
|
+
freshness
|
|
4124
|
+
});
|
|
4125
|
+
return {
|
|
4126
|
+
workspace_id: workspaceId,
|
|
4127
|
+
root_path: rootPath,
|
|
4128
|
+
project_ref: projectRef,
|
|
4129
|
+
project_id: projectId,
|
|
4130
|
+
resolved_by: selected.resolved_by,
|
|
4131
|
+
health,
|
|
4132
|
+
warnings,
|
|
4133
|
+
recommended_next_calls: getWorkspaceRecommendedNextCalls(health),
|
|
4134
|
+
freshness,
|
|
4135
|
+
coverage,
|
|
4136
|
+
last_indexed_commit: lastIndexedCommit,
|
|
4137
|
+
current_commit: currentCommit,
|
|
4138
|
+
pending_changes: pendingChanges ?? null,
|
|
4139
|
+
grounded_to_workspace: health === "healthy"
|
|
4140
|
+
};
|
|
4141
|
+
}
|
|
3991
4142
|
async function resolveProjectRef(explicit) {
|
|
3992
4143
|
if (explicit?.trim()) {
|
|
3993
4144
|
const requestedRef = explicit.trim();
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
cachedProjectRef = resolved.slug || resolved.name || resolved.id;
|
|
3997
|
-
return cachedProjectRef;
|
|
3998
|
-
} catch {
|
|
3999
|
-
cachedProjectRef = requestedRef;
|
|
4000
|
-
return requestedRef;
|
|
4001
|
-
}
|
|
4145
|
+
const resolved = await resolveProjectDescriptor(requestedRef);
|
|
4146
|
+
return resolved.project_ref;
|
|
4002
4147
|
}
|
|
4003
4148
|
if (cachedProjectRef) return cachedProjectRef;
|
|
4004
4149
|
try {
|
|
@@ -4101,7 +4246,9 @@ function buildAbstain(args) {
|
|
|
4101
4246
|
reason: args.reason,
|
|
4102
4247
|
message: args.message,
|
|
4103
4248
|
closest_evidence: args.closest_evidence,
|
|
4104
|
-
|
|
4249
|
+
warnings: args.warnings || [],
|
|
4250
|
+
trust_state: args.trust_state,
|
|
4251
|
+
recommended_next_calls: args.recommended_next_calls || ["index.workspace_status", "index.workspace_run", "grep", "context.get_relevant"],
|
|
4105
4252
|
diagnostics: {
|
|
4106
4253
|
claims_evaluated: args.claims_evaluated,
|
|
4107
4254
|
evidence_items_found: args.evidence_items_found,
|
|
@@ -4177,6 +4324,86 @@ function formatCanonicalMemoryResults(rawResults) {
|
|
|
4177
4324
|
};
|
|
4178
4325
|
});
|
|
4179
4326
|
}
|
|
4327
|
+
function normalizeLooseText(value) {
|
|
4328
|
+
return String(value || "").trim().toLowerCase().replace(/\s+/g, " ");
|
|
4329
|
+
}
|
|
4330
|
+
function resolveForgetQueryCandidates(rawResults, query) {
|
|
4331
|
+
const normalizedQuery = normalizeLooseText(query);
|
|
4332
|
+
const candidates = normalizeCanonicalResults(rawResults).map((result) => {
|
|
4333
|
+
const memory = result?.memory || result;
|
|
4334
|
+
const metadata = memory?.metadata || {};
|
|
4335
|
+
return {
|
|
4336
|
+
id: String(memory?.id || "").trim(),
|
|
4337
|
+
content: normalizeLooseText(memory?.content),
|
|
4338
|
+
normalized_content: normalizeLooseText(metadata?.normalized_content),
|
|
4339
|
+
canonical_content: normalizeLooseText(metadata?.canonical_content),
|
|
4340
|
+
similarity: typeof result?.similarity === "number" ? result.similarity : typeof result?.score === "number" ? result.score : 0
|
|
4341
|
+
};
|
|
4342
|
+
}).filter((candidate) => candidate.id);
|
|
4343
|
+
const exactMatches = candidates.filter(
|
|
4344
|
+
(candidate) => candidate.id.toLowerCase() === normalizedQuery || candidate.content === normalizedQuery || candidate.normalized_content === normalizedQuery || candidate.canonical_content === normalizedQuery
|
|
4345
|
+
);
|
|
4346
|
+
if (exactMatches.length > 0) {
|
|
4347
|
+
return { memory_ids: [...new Set(exactMatches.map((candidate) => candidate.id))], resolved_by: "exact" };
|
|
4348
|
+
}
|
|
4349
|
+
const substringMatches = candidates.filter((candidate) => {
|
|
4350
|
+
const haystacks = [candidate.content, candidate.normalized_content, candidate.canonical_content].filter(Boolean);
|
|
4351
|
+
return haystacks.some(
|
|
4352
|
+
(value) => value.includes(normalizedQuery) || normalizedQuery.length >= 12 && normalizedQuery.includes(value)
|
|
4353
|
+
);
|
|
4354
|
+
});
|
|
4355
|
+
if (substringMatches.length === 1) {
|
|
4356
|
+
return { memory_ids: [substringMatches[0].id], resolved_by: "substring" };
|
|
4357
|
+
}
|
|
4358
|
+
if (substringMatches.length > 1) {
|
|
4359
|
+
return {
|
|
4360
|
+
memory_ids: [],
|
|
4361
|
+
resolved_by: "ambiguous",
|
|
4362
|
+
warning: "Query matched multiple memories by recognizable text. Use memory_id or a more specific query."
|
|
4363
|
+
};
|
|
4364
|
+
}
|
|
4365
|
+
const ranked = [...candidates].sort((a, b) => b.similarity - a.similarity);
|
|
4366
|
+
if (ranked[0] && ranked[0].similarity >= 0.9 && (!ranked[1] || ranked[0].similarity - ranked[1].similarity >= 0.08)) {
|
|
4367
|
+
return { memory_ids: [ranked[0].id], resolved_by: "high_confidence" };
|
|
4368
|
+
}
|
|
4369
|
+
return { memory_ids: [], resolved_by: "none", warning: "Query did not resolve to a reliable memory match. No memories were changed." };
|
|
4370
|
+
}
|
|
4371
|
+
async function searchMemoriesForContextQuery(args) {
|
|
4372
|
+
return whisper.searchMemoriesSOTA({
|
|
4373
|
+
project: args.project,
|
|
4374
|
+
query: args.query,
|
|
4375
|
+
user_id: args.user_id,
|
|
4376
|
+
session_id: args.session_id,
|
|
4377
|
+
top_k: args.top_k ?? 5,
|
|
4378
|
+
include_relations: false,
|
|
4379
|
+
include_pending: true
|
|
4380
|
+
});
|
|
4381
|
+
}
|
|
4382
|
+
async function runContextQueryMemoryRescue(args) {
|
|
4383
|
+
const scoped = await searchMemoriesForContextQuery(args);
|
|
4384
|
+
const scopedResults = formatCanonicalMemoryResults(scoped);
|
|
4385
|
+
if (scopedResults.length > 0) {
|
|
4386
|
+
return { results: scopedResults, rescue_mode: "scoped" };
|
|
4387
|
+
}
|
|
4388
|
+
if (args.user_id || args.session_id) {
|
|
4389
|
+
return { results: [], rescue_mode: null };
|
|
4390
|
+
}
|
|
4391
|
+
const broad = await searchMemoriesForContextQuery({
|
|
4392
|
+
project: args.project,
|
|
4393
|
+
query: args.query,
|
|
4394
|
+
top_k: args.top_k
|
|
4395
|
+
});
|
|
4396
|
+
return { results: formatCanonicalMemoryResults(broad), rescue_mode: formatCanonicalMemoryResults(broad).length > 0 ? "project_broad" : null };
|
|
4397
|
+
}
|
|
4398
|
+
function renderContextQueryMemoryRescue(args) {
|
|
4399
|
+
const lines = args.results.map(
|
|
4400
|
+
(result, index) => `${index + 1}. [${result.memory_type || "memory"}, score: ${result.similarity ?? "n/a"}] ${result.content}`
|
|
4401
|
+
);
|
|
4402
|
+
const scopeLabel = args.rescue_mode === "project_broad" ? `project=${args.project}, project_broad_memory_rescue=true` : `project=${args.project}, user=${args.scope.userId}, session=${args.scope.sessionId}, scoped_memory_rescue=true`;
|
|
4403
|
+
return `Found ${args.results.length} memory result(s) (${scopeLabel}):
|
|
4404
|
+
|
|
4405
|
+
${lines.join("\n\n")}`;
|
|
4406
|
+
}
|
|
4180
4407
|
function likelyEmbeddingFailure(error) {
|
|
4181
4408
|
const message = String(error?.message || error || "").toLowerCase();
|
|
4182
4409
|
return message.includes("embedding") || message.includes("vector") || message.includes("timeout") || message.includes("timed out") || message.includes("temporarily unavailable");
|
|
@@ -4218,6 +4445,186 @@ async function queryWithDegradedFallback(params) {
|
|
|
4218
4445
|
};
|
|
4219
4446
|
}
|
|
4220
4447
|
}
|
|
4448
|
+
function collectCodeFiles(rootPath, allowedExts, maxFiles) {
|
|
4449
|
+
const files = [];
|
|
4450
|
+
function collect(dir) {
|
|
4451
|
+
if (files.length >= maxFiles) return;
|
|
4452
|
+
let entries;
|
|
4453
|
+
try {
|
|
4454
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
4455
|
+
} catch {
|
|
4456
|
+
return;
|
|
4457
|
+
}
|
|
4458
|
+
for (const entry of entries) {
|
|
4459
|
+
if (files.length >= maxFiles) break;
|
|
4460
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
4461
|
+
const full = join(dir, entry.name);
|
|
4462
|
+
if (entry.isDirectory()) collect(full);
|
|
4463
|
+
else if (entry.isFile()) {
|
|
4464
|
+
const ext = extname(entry.name).replace(".", "");
|
|
4465
|
+
if (allowedExts.has(ext)) files.push(full);
|
|
4466
|
+
}
|
|
4467
|
+
}
|
|
4468
|
+
}
|
|
4469
|
+
collect(rootPath);
|
|
4470
|
+
return files;
|
|
4471
|
+
}
|
|
4472
|
+
function tokenizeQueryForLexicalRescue(query) {
|
|
4473
|
+
const stopWords = /* @__PURE__ */ new Set(["where", "what", "show", "find", "logic", "code", "file", "files", "handled", "handling"]);
|
|
4474
|
+
return Array.from(new Set(
|
|
4475
|
+
query.toLowerCase().split(/[^a-z0-9/_-]+/).map((token) => token.trim()).filter((token) => token.length >= 3 && !stopWords.has(token))
|
|
4476
|
+
));
|
|
4477
|
+
}
|
|
4478
|
+
function buildLexicalRescueResults(args) {
|
|
4479
|
+
const tokens = tokenizeQueryForLexicalRescue(args.query);
|
|
4480
|
+
if (tokens.length === 0) return [];
|
|
4481
|
+
const scored = args.documents.map((doc) => {
|
|
4482
|
+
const haystack = `${doc.id}
|
|
4483
|
+
${doc.content}
|
|
4484
|
+
${doc.raw_content}`.toLowerCase();
|
|
4485
|
+
const tokenHits = tokens.filter((token) => haystack.includes(token));
|
|
4486
|
+
const uniqueHits = tokenHits.length;
|
|
4487
|
+
const exactPhraseBonus = haystack.includes(args.query.toLowerCase()) ? 0.25 : 0;
|
|
4488
|
+
const score = uniqueHits === 0 ? 0 : Math.min(0.95, uniqueHits / tokens.length + exactPhraseBonus);
|
|
4489
|
+
return {
|
|
4490
|
+
id: doc.id,
|
|
4491
|
+
score,
|
|
4492
|
+
content: doc.content,
|
|
4493
|
+
snippet: doc.content.split("\n").find((line) => line.trim().length > 10)?.slice(0, 200) || doc.id,
|
|
4494
|
+
search_mode: "lexical_rescue"
|
|
4495
|
+
};
|
|
4496
|
+
});
|
|
4497
|
+
return scored.filter((result) => result.score > 0).sort((a, b) => b.score - a.score).slice(0, args.top_k);
|
|
4498
|
+
}
|
|
4499
|
+
async function runSearchCodeSearch(args) {
|
|
4500
|
+
const topK = args.top_k ?? 10;
|
|
4501
|
+
const requestedThreshold = args.threshold ?? 0.2;
|
|
4502
|
+
const semanticDocuments = args.documents.map((doc) => ({ id: doc.id, content: doc.content }));
|
|
4503
|
+
const defaultResponse = await args.semantic_search({
|
|
4504
|
+
query: args.query,
|
|
4505
|
+
documents: semanticDocuments,
|
|
4506
|
+
top_k: topK,
|
|
4507
|
+
threshold: requestedThreshold
|
|
4508
|
+
});
|
|
4509
|
+
if (defaultResponse.results?.length) {
|
|
4510
|
+
return {
|
|
4511
|
+
results: defaultResponse.results.map((result) => ({ ...result, search_mode: "semantic" })),
|
|
4512
|
+
mode: "semantic",
|
|
4513
|
+
threshold_used: requestedThreshold,
|
|
4514
|
+
fallback_used: false,
|
|
4515
|
+
fallback_reason: null
|
|
4516
|
+
};
|
|
4517
|
+
}
|
|
4518
|
+
const adaptiveThreshold = Math.max(0.05, Math.min(requestedThreshold, requestedThreshold / 2));
|
|
4519
|
+
if (adaptiveThreshold < requestedThreshold) {
|
|
4520
|
+
const adaptiveResponse = await args.semantic_search({
|
|
4521
|
+
query: args.query,
|
|
4522
|
+
documents: semanticDocuments,
|
|
4523
|
+
top_k: topK,
|
|
4524
|
+
threshold: adaptiveThreshold
|
|
4525
|
+
});
|
|
4526
|
+
if (adaptiveResponse.results?.length) {
|
|
4527
|
+
return {
|
|
4528
|
+
results: adaptiveResponse.results.map((result) => ({ ...result, search_mode: "adaptive_semantic" })),
|
|
4529
|
+
mode: "adaptive_semantic",
|
|
4530
|
+
threshold_used: adaptiveThreshold,
|
|
4531
|
+
fallback_used: true,
|
|
4532
|
+
fallback_reason: `No results at threshold ${requestedThreshold}; lowered to ${adaptiveThreshold}.`
|
|
4533
|
+
};
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4536
|
+
const lexicalResults = buildLexicalRescueResults({
|
|
4537
|
+
query: args.query,
|
|
4538
|
+
documents: args.documents,
|
|
4539
|
+
top_k: topK
|
|
4540
|
+
});
|
|
4541
|
+
return {
|
|
4542
|
+
results: lexicalResults,
|
|
4543
|
+
mode: "lexical_rescue",
|
|
4544
|
+
threshold_used: null,
|
|
4545
|
+
fallback_used: true,
|
|
4546
|
+
fallback_reason: lexicalResults.length ? "Semantic ranking returned no matches; lexical rescue over local file paths and content was used." : "Semantic ranking returned no matches and lexical rescue found no strong candidates."
|
|
4547
|
+
};
|
|
4548
|
+
}
|
|
4549
|
+
async function runSearchCodeTool(args) {
|
|
4550
|
+
const rootPath = canonicalizeWorkspacePath(args.path);
|
|
4551
|
+
const workspace = await resolveWorkspaceTrust({ path: rootPath });
|
|
4552
|
+
const allowedExts = args.file_types ? new Set(args.file_types) : CODE_EXTENSIONS;
|
|
4553
|
+
const files = collectCodeFiles(rootPath, allowedExts, args.max_files ?? 150);
|
|
4554
|
+
const sharedWarnings = [...workspace.warnings];
|
|
4555
|
+
if (files.length === 0) {
|
|
4556
|
+
return {
|
|
4557
|
+
tool: "search_code",
|
|
4558
|
+
query: args.query,
|
|
4559
|
+
path: rootPath,
|
|
4560
|
+
results: [],
|
|
4561
|
+
count: 0,
|
|
4562
|
+
warnings: sharedWarnings,
|
|
4563
|
+
diagnostics: {
|
|
4564
|
+
workspace_id: workspace.workspace_id,
|
|
4565
|
+
root_path: workspace.root_path,
|
|
4566
|
+
project_ref: workspace.project_ref,
|
|
4567
|
+
project_id: workspace.project_id,
|
|
4568
|
+
index_health: workspace.health,
|
|
4569
|
+
grounded_to_workspace: workspace.grounded_to_workspace,
|
|
4570
|
+
threshold_requested: args.threshold ?? 0.2,
|
|
4571
|
+
threshold_used: null,
|
|
4572
|
+
fallback_used: false,
|
|
4573
|
+
fallback_reason: null,
|
|
4574
|
+
search_mode: "semantic",
|
|
4575
|
+
warnings: sharedWarnings
|
|
4576
|
+
}
|
|
4577
|
+
};
|
|
4578
|
+
}
|
|
4579
|
+
const documents = [];
|
|
4580
|
+
for (const filePath of files) {
|
|
4581
|
+
try {
|
|
4582
|
+
const stat = statSync(filePath);
|
|
4583
|
+
if (stat.size > 500 * 1024) continue;
|
|
4584
|
+
const content = readFileSync(filePath, "utf-8");
|
|
4585
|
+
const relPath = relative(rootPath, filePath).replace(/\\/g, "/");
|
|
4586
|
+
documents.push({ id: relPath, content: extractSignature(relPath, content), raw_content: content });
|
|
4587
|
+
} catch {
|
|
4588
|
+
}
|
|
4589
|
+
}
|
|
4590
|
+
const searchResult = await runSearchCodeSearch({
|
|
4591
|
+
query: args.query,
|
|
4592
|
+
documents,
|
|
4593
|
+
top_k: args.top_k,
|
|
4594
|
+
threshold: args.threshold,
|
|
4595
|
+
semantic_search: (params) => whisper.semanticSearch(params)
|
|
4596
|
+
});
|
|
4597
|
+
if (searchResult.mode === "lexical_rescue") {
|
|
4598
|
+
sharedWarnings.push("Local lexical rescue was used because semantic search returned no matches.");
|
|
4599
|
+
} else if (searchResult.mode === "adaptive_semantic") {
|
|
4600
|
+
sharedWarnings.push(searchResult.fallback_reason || "Adaptive semantic thresholding was used.");
|
|
4601
|
+
}
|
|
4602
|
+
if (!workspace.grounded_to_workspace && workspace.health !== "unbound" && workspace.health !== "unindexed") {
|
|
4603
|
+
sharedWarnings.push("Local code search is live, but project-backed retrieval may disagree until the workspace is re-indexed.");
|
|
4604
|
+
}
|
|
4605
|
+
return {
|
|
4606
|
+
tool: "search_code",
|
|
4607
|
+
query: args.query,
|
|
4608
|
+
path: rootPath,
|
|
4609
|
+
results: searchResult.results,
|
|
4610
|
+
count: searchResult.results.length,
|
|
4611
|
+
warnings: sharedWarnings,
|
|
4612
|
+
diagnostics: {
|
|
4613
|
+
workspace_id: workspace.workspace_id,
|
|
4614
|
+
root_path: workspace.root_path,
|
|
4615
|
+
project_ref: workspace.project_ref,
|
|
4616
|
+
project_id: workspace.project_id,
|
|
4617
|
+
index_health: workspace.health,
|
|
4618
|
+
grounded_to_workspace: workspace.grounded_to_workspace,
|
|
4619
|
+
threshold_requested: args.threshold ?? 0.2,
|
|
4620
|
+
threshold_used: searchResult.threshold_used,
|
|
4621
|
+
fallback_used: searchResult.fallback_used,
|
|
4622
|
+
fallback_reason: searchResult.fallback_reason,
|
|
4623
|
+
search_mode: searchResult.mode,
|
|
4624
|
+
warnings: sharedWarnings
|
|
4625
|
+
}
|
|
4626
|
+
};
|
|
4627
|
+
}
|
|
4221
4628
|
function getLocalAllowlistRoots() {
|
|
4222
4629
|
const fromEnv = (process.env.WHISPER_LOCAL_ALLOWLIST || "").split(",").map((v) => v.trim()).filter(Boolean);
|
|
4223
4630
|
if (fromEnv.length > 0) return fromEnv;
|
|
@@ -4297,7 +4704,8 @@ async function ingestLocalPath(params) {
|
|
|
4297
4704
|
}
|
|
4298
4705
|
collect(rootPath);
|
|
4299
4706
|
const manifest = loadIngestManifest();
|
|
4300
|
-
const
|
|
4707
|
+
const canonicalRootPath = canonicalizeWorkspacePath(rootPath);
|
|
4708
|
+
const workspaceId = getWorkspaceIdForPath(canonicalRootPath);
|
|
4301
4709
|
if (!manifest[workspaceId]) manifest[workspaceId] = { last_run_at: (/* @__PURE__ */ new Date(0)).toISOString(), files: {} };
|
|
4302
4710
|
const docs = [];
|
|
4303
4711
|
const skipped = [];
|
|
@@ -4333,6 +4741,10 @@ async function ingestLocalPath(params) {
|
|
|
4333
4741
|
}
|
|
4334
4742
|
manifest[workspaceId].last_run_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
4335
4743
|
saveIngestManifest(manifest);
|
|
4744
|
+
const state = loadState();
|
|
4745
|
+
const workspace = getWorkspaceState(state, workspaceId);
|
|
4746
|
+
workspace.root_path = canonicalRootPath;
|
|
4747
|
+
saveState(state);
|
|
4336
4748
|
appendFileSync(
|
|
4337
4749
|
AUDIT_LOG_PATH,
|
|
4338
4750
|
`${(/* @__PURE__ */ new Date()).toISOString()} local_ingest workspace=${workspaceId} root_hash=${createHash("sha256").update(rootPath).digest("hex").slice(0, 16)} files=${docs.length}
|
|
@@ -4488,22 +4900,25 @@ server.tool(
|
|
|
4488
4900
|
},
|
|
4489
4901
|
async ({ path, workspace_id, project }) => {
|
|
4490
4902
|
try {
|
|
4491
|
-
const
|
|
4903
|
+
const rootPath = canonicalizeWorkspacePath(path);
|
|
4904
|
+
const workspaceId = getWorkspaceIdForPath(rootPath, workspace_id);
|
|
4492
4905
|
const state = loadState();
|
|
4493
4906
|
const existed = Boolean(state.workspaces[workspaceId]);
|
|
4494
|
-
const
|
|
4495
|
-
const resolvedProject = await resolveProjectRef(project);
|
|
4496
|
-
const resolvedBy = project?.trim() ? "explicit_project" : DEFAULT_PROJECT ? "env_default" : resolvedProject ? "auto_first_project" : "unresolved";
|
|
4497
|
-
saveState(state);
|
|
4907
|
+
const trust = await resolveWorkspaceTrust({ path: rootPath, workspace_id, project });
|
|
4498
4908
|
const payload = {
|
|
4499
4909
|
workspace_id: workspaceId,
|
|
4500
|
-
|
|
4910
|
+
root_path: trust.root_path,
|
|
4911
|
+
project_ref: trust.project_ref,
|
|
4912
|
+
project_id: trust.project_id,
|
|
4501
4913
|
created: !existed,
|
|
4502
|
-
resolved_by:
|
|
4914
|
+
resolved_by: trust.resolved_by,
|
|
4915
|
+
health: trust.health,
|
|
4916
|
+
warnings: trust.warnings,
|
|
4917
|
+
recommended_next_calls: trust.recommended_next_calls,
|
|
4503
4918
|
index_state: {
|
|
4504
|
-
last_indexed_at:
|
|
4505
|
-
last_indexed_commit:
|
|
4506
|
-
coverage:
|
|
4919
|
+
last_indexed_at: trust.freshness.last_indexed_at,
|
|
4920
|
+
last_indexed_commit: trust.last_indexed_commit,
|
|
4921
|
+
coverage: trust.coverage
|
|
4507
4922
|
}
|
|
4508
4923
|
};
|
|
4509
4924
|
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
@@ -4521,24 +4936,22 @@ server.tool(
|
|
|
4521
4936
|
},
|
|
4522
4937
|
async ({ workspace_id, path }) => {
|
|
4523
4938
|
try {
|
|
4524
|
-
const
|
|
4525
|
-
const workspaceId = getWorkspaceIdForPath(rootPath, workspace_id);
|
|
4526
|
-
const state = loadState();
|
|
4527
|
-
const workspace = getWorkspaceState(state, workspaceId);
|
|
4528
|
-
const lastIndexedAt = workspace.index_metadata?.last_indexed_at;
|
|
4529
|
-
const ageHours = lastIndexedAt ? (Date.now() - new Date(lastIndexedAt).getTime()) / (60 * 60 * 1e3) : null;
|
|
4530
|
-
const stale = ageHours === null ? true : ageHours > 168;
|
|
4939
|
+
const trust = await resolveWorkspaceTrust({ path, workspace_id });
|
|
4531
4940
|
const payload = {
|
|
4532
|
-
workspace_id:
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4941
|
+
workspace_id: trust.workspace_id,
|
|
4942
|
+
root_path: trust.root_path,
|
|
4943
|
+
project_ref: trust.project_ref,
|
|
4944
|
+
project_id: trust.project_id,
|
|
4945
|
+
resolved_by: trust.resolved_by,
|
|
4946
|
+
health: trust.health,
|
|
4947
|
+
warnings: trust.warnings,
|
|
4948
|
+
recommended_next_calls: trust.recommended_next_calls,
|
|
4949
|
+
freshness: trust.freshness,
|
|
4950
|
+
coverage: trust.coverage,
|
|
4951
|
+
last_indexed_commit: trust.last_indexed_commit,
|
|
4952
|
+
current_commit: trust.current_commit,
|
|
4953
|
+
pending_changes: trust.pending_changes,
|
|
4954
|
+
grounded_to_workspace: trust.grounded_to_workspace
|
|
4542
4955
|
};
|
|
4543
4956
|
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
4544
4957
|
} catch (error) {
|
|
@@ -4557,13 +4970,14 @@ server.tool(
|
|
|
4557
4970
|
},
|
|
4558
4971
|
async ({ workspace_id, path, mode, max_files }) => {
|
|
4559
4972
|
try {
|
|
4560
|
-
const rootPath = path
|
|
4973
|
+
const rootPath = canonicalizeWorkspacePath(path);
|
|
4561
4974
|
const workspaceId = getWorkspaceIdForPath(rootPath, workspace_id);
|
|
4562
4975
|
const state = loadState();
|
|
4563
4976
|
const workspace = getWorkspaceState(state, workspaceId);
|
|
4564
4977
|
const fileStats = countCodeFiles(rootPath, max_files);
|
|
4565
4978
|
const coverage = Math.max(0, Math.min(1, fileStats.total / Math.max(1, max_files)));
|
|
4566
4979
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4980
|
+
workspace.root_path = rootPath;
|
|
4567
4981
|
workspace.index_metadata = {
|
|
4568
4982
|
last_indexed_at: now,
|
|
4569
4983
|
last_indexed_commit: getGitHead(rootPath),
|
|
@@ -4572,6 +4986,7 @@ server.tool(
|
|
|
4572
4986
|
saveState(state);
|
|
4573
4987
|
const payload = {
|
|
4574
4988
|
workspace_id: workspaceId,
|
|
4989
|
+
root_path: rootPath,
|
|
4575
4990
|
mode,
|
|
4576
4991
|
indexed_files: fileStats.total,
|
|
4577
4992
|
skipped_files: fileStats.skipped,
|
|
@@ -4636,23 +5051,25 @@ server.tool(
|
|
|
4636
5051
|
},
|
|
4637
5052
|
async ({ question, workspace_id, project, top_k, include_memories, include_graph, session_id, user_id }) => {
|
|
4638
5053
|
try {
|
|
4639
|
-
const
|
|
4640
|
-
|
|
4641
|
-
if (!resolvedProject) {
|
|
5054
|
+
const trust = await resolveWorkspaceTrust({ workspace_id, project });
|
|
5055
|
+
if (trust.health === "unbound" || trust.health === "unindexed" || !trust.project_ref) {
|
|
4642
5056
|
const payload2 = {
|
|
4643
5057
|
question,
|
|
4644
|
-
workspace_id:
|
|
5058
|
+
workspace_id: trust.workspace_id,
|
|
5059
|
+
trust_state: trust,
|
|
5060
|
+
grounded_to_workspace: false,
|
|
4645
5061
|
total_results: 0,
|
|
4646
5062
|
context: "",
|
|
4647
5063
|
evidence: [],
|
|
4648
5064
|
used_context_ids: [],
|
|
4649
5065
|
latency_ms: 0,
|
|
4650
|
-
|
|
5066
|
+
warnings: trust.warnings,
|
|
5067
|
+
recommended_next_calls: trust.recommended_next_calls
|
|
4651
5068
|
};
|
|
4652
5069
|
return { content: [{ type: "text", text: JSON.stringify(payload2, null, 2) }] };
|
|
4653
5070
|
}
|
|
4654
5071
|
const queryResult = await queryWithDegradedFallback({
|
|
4655
|
-
project:
|
|
5072
|
+
project: trust.project_ref,
|
|
4656
5073
|
query: question,
|
|
4657
5074
|
top_k,
|
|
4658
5075
|
include_memories,
|
|
@@ -4661,10 +5078,12 @@ server.tool(
|
|
|
4661
5078
|
user_id
|
|
4662
5079
|
});
|
|
4663
5080
|
const response = queryResult.response;
|
|
4664
|
-
const evidence = (response.results || []).map((r) => toEvidenceRef(r,
|
|
5081
|
+
const evidence = (response.results || []).map((r) => toEvidenceRef(r, trust.workspace_id, "semantic"));
|
|
4665
5082
|
const payload = {
|
|
4666
5083
|
question,
|
|
4667
|
-
workspace_id:
|
|
5084
|
+
workspace_id: trust.workspace_id,
|
|
5085
|
+
trust_state: trust,
|
|
5086
|
+
grounded_to_workspace: trust.grounded_to_workspace,
|
|
4668
5087
|
total_results: response.meta?.total || evidence.length,
|
|
4669
5088
|
context: response.context || "",
|
|
4670
5089
|
evidence,
|
|
@@ -4672,7 +5091,9 @@ server.tool(
|
|
|
4672
5091
|
latency_ms: response.meta?.latency_ms || 0,
|
|
4673
5092
|
degraded_mode: queryResult.degraded_mode,
|
|
4674
5093
|
degraded_reason: queryResult.degraded_reason,
|
|
4675
|
-
recommendation: queryResult.recommendation
|
|
5094
|
+
recommendation: queryResult.recommendation,
|
|
5095
|
+
warnings: trust.warnings,
|
|
5096
|
+
recommended_next_calls: trust.recommended_next_calls
|
|
4676
5097
|
};
|
|
4677
5098
|
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
4678
5099
|
} catch (error) {
|
|
@@ -4692,20 +5113,22 @@ server.tool(
|
|
|
4692
5113
|
},
|
|
4693
5114
|
async ({ claim, workspace_id, project, context_ids, strict }) => {
|
|
4694
5115
|
try {
|
|
4695
|
-
const
|
|
4696
|
-
|
|
4697
|
-
if (!resolvedProject) {
|
|
5116
|
+
const trust = await resolveWorkspaceTrust({ workspace_id, project });
|
|
5117
|
+
if (trust.health !== "healthy" || !trust.project_ref) {
|
|
4698
5118
|
const payload2 = {
|
|
4699
5119
|
verdict: "unsupported",
|
|
4700
5120
|
confidence: 0,
|
|
4701
5121
|
evidence: [],
|
|
4702
|
-
|
|
4703
|
-
|
|
5122
|
+
trust_state: trust,
|
|
5123
|
+
warnings: trust.warnings,
|
|
5124
|
+
recommended_next_calls: trust.recommended_next_calls,
|
|
5125
|
+
missing_requirements: trust.warnings.length ? trust.warnings : ["Workspace trust requirements were not met."],
|
|
5126
|
+
explanation: "Verifier did not run because workspace grounding is insufficient."
|
|
4704
5127
|
};
|
|
4705
5128
|
return { content: [{ type: "text", text: JSON.stringify(payload2, null, 2) }] };
|
|
4706
5129
|
}
|
|
4707
5130
|
const response = await whisper.query({
|
|
4708
|
-
project:
|
|
5131
|
+
project: trust.project_ref,
|
|
4709
5132
|
query: claim,
|
|
4710
5133
|
top_k: strict ? 8 : 12,
|
|
4711
5134
|
include_memories: true,
|
|
@@ -4714,7 +5137,7 @@ server.tool(
|
|
|
4714
5137
|
const filtered = (response.results || []).filter(
|
|
4715
5138
|
(r) => !context_ids || context_ids.length === 0 || context_ids.includes(String(r.id))
|
|
4716
5139
|
);
|
|
4717
|
-
const evidence = filtered.map((r) => toEvidenceRef(r,
|
|
5140
|
+
const evidence = filtered.map((r) => toEvidenceRef(r, trust.workspace_id, "semantic"));
|
|
4718
5141
|
const directEvidence = evidence.filter((e) => e.score >= (strict ? 0.7 : 0.6));
|
|
4719
5142
|
const weakEvidence = evidence.filter((e) => e.score >= (strict ? 0.45 : 0.35));
|
|
4720
5143
|
let verdict = "unsupported";
|
|
@@ -4724,6 +5147,9 @@ server.tool(
|
|
|
4724
5147
|
verdict,
|
|
4725
5148
|
confidence: evidence.length ? Math.max(...evidence.map((e) => e.score)) : 0,
|
|
4726
5149
|
evidence: verdict === "supported" ? directEvidence : weakEvidence,
|
|
5150
|
+
trust_state: trust,
|
|
5151
|
+
warnings: trust.warnings,
|
|
5152
|
+
recommended_next_calls: trust.recommended_next_calls,
|
|
4727
5153
|
missing_requirements: verdict === "supported" ? [] : verdict === "partial" ? ["No direct evidence spans met strict threshold."] : ["No sufficient supporting evidence found for the claim."],
|
|
4728
5154
|
explanation: verdict === "supported" ? "At least one direct evidence span supports the claim." : verdict === "partial" ? "Some related evidence exists, but direct support is incomplete." : "Retrieved context did not contain sufficient support."
|
|
4729
5155
|
};
|
|
@@ -4754,39 +5180,38 @@ server.tool(
|
|
|
4754
5180
|
},
|
|
4755
5181
|
async ({ question, workspace_id, project, constraints, retrieval }) => {
|
|
4756
5182
|
try {
|
|
4757
|
-
const workspaceId = getWorkspaceId(workspace_id);
|
|
4758
5183
|
const requireCitations = constraints?.require_citations ?? true;
|
|
4759
5184
|
const minEvidenceItems = constraints?.min_evidence_items ?? 2;
|
|
4760
5185
|
const minConfidence = constraints?.min_confidence ?? 0.65;
|
|
4761
5186
|
const maxStalenessHours = constraints?.max_staleness_hours ?? 168;
|
|
4762
5187
|
const topK = retrieval?.top_k ?? 12;
|
|
4763
|
-
const
|
|
4764
|
-
if (!
|
|
5188
|
+
const trust = await resolveWorkspaceTrust({ workspace_id, project, max_staleness_hours: maxStalenessHours });
|
|
5189
|
+
if (trust.health !== "healthy" || !trust.project_ref) {
|
|
5190
|
+
const reason = trust.health === "stale" || trust.health === "drifted" ? "stale_index" : "no_retrieval_hits";
|
|
4765
5191
|
const abstain = buildAbstain({
|
|
4766
|
-
reason
|
|
4767
|
-
message:
|
|
5192
|
+
reason,
|
|
5193
|
+
message: trust.warnings[0] || "Workspace trust requirements were not met.",
|
|
4768
5194
|
closest_evidence: [],
|
|
4769
5195
|
claims_evaluated: 1,
|
|
4770
5196
|
evidence_items_found: 0,
|
|
4771
5197
|
min_required: minEvidenceItems,
|
|
4772
|
-
index_fresh:
|
|
5198
|
+
index_fresh: trust.health === "healthy",
|
|
5199
|
+
warnings: trust.warnings,
|
|
5200
|
+
trust_state: trust,
|
|
5201
|
+
recommended_next_calls: trust.recommended_next_calls
|
|
4773
5202
|
});
|
|
4774
5203
|
return { content: [{ type: "text", text: JSON.stringify(abstain, null, 2) }] };
|
|
4775
5204
|
}
|
|
4776
5205
|
const response = await whisper.query({
|
|
4777
|
-
project:
|
|
5206
|
+
project: trust.project_ref,
|
|
4778
5207
|
query: question,
|
|
4779
5208
|
top_k: topK,
|
|
4780
5209
|
include_memories: true,
|
|
4781
5210
|
include_graph: true
|
|
4782
5211
|
});
|
|
4783
|
-
const evidence = (response.results || []).map((r) => toEvidenceRef(r,
|
|
5212
|
+
const evidence = (response.results || []).map((r) => toEvidenceRef(r, trust.workspace_id, "semantic"));
|
|
4784
5213
|
const sorted = evidence.sort((a, b) => b.score - a.score);
|
|
4785
5214
|
const confidence = sorted.length ? sorted[0].score : 0;
|
|
4786
|
-
const state = loadState();
|
|
4787
|
-
const workspace = getWorkspaceState(state, workspaceId);
|
|
4788
|
-
const lastIndexedAt = workspace.index_metadata?.last_indexed_at;
|
|
4789
|
-
const indexFresh = !lastIndexedAt || Date.now() - new Date(lastIndexedAt).getTime() <= maxStalenessHours * 60 * 60 * 1e3;
|
|
4790
5215
|
if (sorted.length === 0) {
|
|
4791
5216
|
const abstain = buildAbstain({
|
|
4792
5217
|
reason: "no_retrieval_hits",
|
|
@@ -4795,24 +5220,15 @@ server.tool(
|
|
|
4795
5220
|
claims_evaluated: 1,
|
|
4796
5221
|
evidence_items_found: 0,
|
|
4797
5222
|
min_required: minEvidenceItems,
|
|
4798
|
-
index_fresh:
|
|
5223
|
+
index_fresh: trust.health === "healthy",
|
|
5224
|
+
warnings: trust.warnings,
|
|
5225
|
+
trust_state: trust,
|
|
5226
|
+
recommended_next_calls: trust.recommended_next_calls
|
|
4799
5227
|
});
|
|
4800
5228
|
return { content: [{ type: "text", text: JSON.stringify(abstain, null, 2) }] };
|
|
4801
5229
|
}
|
|
4802
5230
|
const supportedEvidence = sorted.filter((e) => e.score >= minConfidence);
|
|
4803
5231
|
const verdict = supportedEvidence.length >= 1 ? "supported" : "partial";
|
|
4804
|
-
if (!indexFresh) {
|
|
4805
|
-
const abstain = buildAbstain({
|
|
4806
|
-
reason: "stale_index",
|
|
4807
|
-
message: "Index freshness requirement not met. Re-index before answering.",
|
|
4808
|
-
closest_evidence: sorted.slice(0, 3),
|
|
4809
|
-
claims_evaluated: 1,
|
|
4810
|
-
evidence_items_found: supportedEvidence.length,
|
|
4811
|
-
min_required: minEvidenceItems,
|
|
4812
|
-
index_fresh: false
|
|
4813
|
-
});
|
|
4814
|
-
return { content: [{ type: "text", text: JSON.stringify(abstain, null, 2) }] };
|
|
4815
|
-
}
|
|
4816
5232
|
if (requireCitations && (verdict !== "supported" || supportedEvidence.length < minEvidenceItems)) {
|
|
4817
5233
|
const abstain = buildAbstain({
|
|
4818
5234
|
reason: "insufficient_evidence",
|
|
@@ -4821,7 +5237,10 @@ server.tool(
|
|
|
4821
5237
|
claims_evaluated: 1,
|
|
4822
5238
|
evidence_items_found: supportedEvidence.length,
|
|
4823
5239
|
min_required: minEvidenceItems,
|
|
4824
|
-
index_fresh: true
|
|
5240
|
+
index_fresh: true,
|
|
5241
|
+
warnings: trust.warnings,
|
|
5242
|
+
trust_state: trust,
|
|
5243
|
+
recommended_next_calls: trust.recommended_next_calls
|
|
4825
5244
|
});
|
|
4826
5245
|
return { content: [{ type: "text", text: JSON.stringify(abstain, null, 2) }] };
|
|
4827
5246
|
}
|
|
@@ -4834,6 +5253,9 @@ server.tool(
|
|
|
4834
5253
|
answer: answerLines.join("\n"),
|
|
4835
5254
|
citations,
|
|
4836
5255
|
confidence,
|
|
5256
|
+
trust_state: trust,
|
|
5257
|
+
warnings: trust.warnings,
|
|
5258
|
+
recommended_next_calls: trust.recommended_next_calls,
|
|
4837
5259
|
verification: {
|
|
4838
5260
|
verdict,
|
|
4839
5261
|
supported_claims: verdict === "supported" ? 1 : 0,
|
|
@@ -4867,6 +5289,7 @@ server.tool(
|
|
|
4867
5289
|
if (!resolvedProject) {
|
|
4868
5290
|
return { content: [{ type: "text", text: "Error: No project resolved. Set WHISPER_PROJECT or pass project." }] };
|
|
4869
5291
|
}
|
|
5292
|
+
const scope = resolveMcpScope({ project: resolvedProject, user_id, session_id });
|
|
4870
5293
|
const automaticMode = include_memories !== false && include_graph !== true && !(chunk_types && chunk_types.length > 0) && max_tokens === void 0 && runtimeClient;
|
|
4871
5294
|
if (automaticMode) {
|
|
4872
5295
|
try {
|
|
@@ -4878,6 +5301,27 @@ server.tool(
|
|
|
4878
5301
|
session_id
|
|
4879
5302
|
});
|
|
4880
5303
|
if (!prepared.items.length) {
|
|
5304
|
+
const memoryRescue = include_memories !== false ? await runContextQueryMemoryRescue({
|
|
5305
|
+
project: resolvedProject,
|
|
5306
|
+
query,
|
|
5307
|
+
user_id: user_id ? scope2.userId : void 0,
|
|
5308
|
+
session_id: session_id ? scope2.sessionId : void 0,
|
|
5309
|
+
top_k
|
|
5310
|
+
}) : { results: [], rescue_mode: null };
|
|
5311
|
+
if (memoryRescue.results.length && memoryRescue.rescue_mode) {
|
|
5312
|
+
return {
|
|
5313
|
+
content: [{
|
|
5314
|
+
type: "text",
|
|
5315
|
+
text: renderContextQueryMemoryRescue({
|
|
5316
|
+
project: resolvedProject,
|
|
5317
|
+
query,
|
|
5318
|
+
scope: scope2,
|
|
5319
|
+
results: memoryRescue.results,
|
|
5320
|
+
rescue_mode: memoryRescue.rescue_mode
|
|
5321
|
+
})
|
|
5322
|
+
}]
|
|
5323
|
+
};
|
|
5324
|
+
}
|
|
4881
5325
|
return { content: [{ type: "text", text: "No relevant context found." }] };
|
|
4882
5326
|
}
|
|
4883
5327
|
const warnings = prepared.retrieval.warnings.length ? `
|
|
@@ -4912,13 +5356,36 @@ ${prepared.context}${warnings}`
|
|
|
4912
5356
|
});
|
|
4913
5357
|
const response2 = queryResult2.response;
|
|
4914
5358
|
if (response2.results.length === 0) {
|
|
5359
|
+
const memoryRescue = include_memories !== false ? await runContextQueryMemoryRescue({
|
|
5360
|
+
project: resolvedProject,
|
|
5361
|
+
query,
|
|
5362
|
+
user_id: user_id ? scope.userId : void 0,
|
|
5363
|
+
session_id: session_id ? scope.sessionId : void 0,
|
|
5364
|
+
top_k
|
|
5365
|
+
}) : { results: [], rescue_mode: null };
|
|
5366
|
+
if (memoryRescue.results.length && memoryRescue.rescue_mode) {
|
|
5367
|
+
return {
|
|
5368
|
+
content: [{
|
|
5369
|
+
type: "text",
|
|
5370
|
+
text: `${renderContextQueryMemoryRescue({
|
|
5371
|
+
project: resolvedProject,
|
|
5372
|
+
query,
|
|
5373
|
+
scope,
|
|
5374
|
+
results: memoryRescue.results,
|
|
5375
|
+
rescue_mode: memoryRescue.rescue_mode
|
|
5376
|
+
})}
|
|
5377
|
+
|
|
5378
|
+
[automatic_runtime]
|
|
5379
|
+
${automaticWarning}`
|
|
5380
|
+
}]
|
|
5381
|
+
};
|
|
5382
|
+
}
|
|
4915
5383
|
return { content: [{ type: "text", text: `No relevant context found.
|
|
4916
5384
|
|
|
4917
5385
|
[automatic_runtime]
|
|
4918
5386
|
${automaticWarning}` }] };
|
|
4919
5387
|
}
|
|
4920
|
-
const
|
|
4921
|
-
const header2 = `Found ${response2.meta.total} results (${response2.meta.latency_ms}ms${response2.meta.cache_hit ? ", cached" : ""}, project=${resolvedProject}, user=${scope2.userId}, session=${scope2.sessionId}):
|
|
5388
|
+
const header2 = `Found ${response2.meta.total} results (${response2.meta.latency_ms}ms${response2.meta.cache_hit ? ", cached" : ""}, project=${resolvedProject}, user=${scope.userId}, session=${scope.sessionId}):
|
|
4922
5389
|
|
|
4923
5390
|
`;
|
|
4924
5391
|
const suffix2 = queryResult2.degraded_mode ? `
|
|
@@ -4942,9 +5409,29 @@ ${automaticWarning}${suffix2}` }] };
|
|
|
4942
5409
|
});
|
|
4943
5410
|
const response = queryResult.response;
|
|
4944
5411
|
if (response.results.length === 0) {
|
|
5412
|
+
const memoryRescue = include_memories !== false ? await runContextQueryMemoryRescue({
|
|
5413
|
+
project: resolvedProject,
|
|
5414
|
+
query,
|
|
5415
|
+
user_id: user_id ? scope.userId : void 0,
|
|
5416
|
+
session_id: session_id ? scope.sessionId : void 0,
|
|
5417
|
+
top_k
|
|
5418
|
+
}) : { results: [], rescue_mode: null };
|
|
5419
|
+
if (memoryRescue.results.length && memoryRescue.rescue_mode) {
|
|
5420
|
+
return {
|
|
5421
|
+
content: [{
|
|
5422
|
+
type: "text",
|
|
5423
|
+
text: renderContextQueryMemoryRescue({
|
|
5424
|
+
project: resolvedProject,
|
|
5425
|
+
query,
|
|
5426
|
+
scope,
|
|
5427
|
+
results: memoryRescue.results,
|
|
5428
|
+
rescue_mode: memoryRescue.rescue_mode
|
|
5429
|
+
})
|
|
5430
|
+
}]
|
|
5431
|
+
};
|
|
5432
|
+
}
|
|
4945
5433
|
return { content: [{ type: "text", text: "No relevant context found." }] };
|
|
4946
5434
|
}
|
|
4947
|
-
const scope = resolveMcpScope({ user_id, session_id });
|
|
4948
5435
|
const header = `Found ${response.meta.total} results (${response.meta.latency_ms}ms${response.meta.cache_hit ? ", cached" : ""}, project=${resolvedProject}, user=${scope.userId}, session=${scope.sessionId}):
|
|
4949
5436
|
|
|
4950
5437
|
`;
|
|
@@ -5587,6 +6074,7 @@ server.tool(
|
|
|
5587
6074
|
return { content: [{ type: "text", text: "Error: target.memory_id or target.query is required." }] };
|
|
5588
6075
|
}
|
|
5589
6076
|
const affectedIds = [];
|
|
6077
|
+
let queryResolution = null;
|
|
5590
6078
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5591
6079
|
const actor = process.env.WHISPER_AGENT_ID || process.env.USERNAME || "api_key_principal";
|
|
5592
6080
|
const resolvedProject = await resolveProjectRef(project);
|
|
@@ -5645,24 +6133,18 @@ server.tool(
|
|
|
5645
6133
|
project: resolvedProject,
|
|
5646
6134
|
query: target.query || "",
|
|
5647
6135
|
top_k: 25,
|
|
5648
|
-
include_relations: false
|
|
5649
|
-
|
|
5650
|
-
const normalizedQuery = String(target.query || "").trim().toLowerCase();
|
|
5651
|
-
const exactMatches = (search.results || []).filter((r) => {
|
|
5652
|
-
const memory = r?.memory || r;
|
|
5653
|
-
const content = String(memory?.content || "").trim().toLowerCase();
|
|
5654
|
-
const memoryId = String(memory?.id || "").trim().toLowerCase();
|
|
5655
|
-
const metadata = memory?.metadata || {};
|
|
5656
|
-
const normalizedContent = String(metadata?.normalized_content || "").trim().toLowerCase();
|
|
5657
|
-
const canonicalContent = String(metadata?.canonical_content || "").trim().toLowerCase();
|
|
5658
|
-
return memoryId === normalizedQuery || content === normalizedQuery || normalizedContent === normalizedQuery || canonicalContent === normalizedQuery;
|
|
6136
|
+
include_relations: false,
|
|
6137
|
+
include_pending: true
|
|
5659
6138
|
});
|
|
5660
|
-
const
|
|
6139
|
+
const resolved = resolveForgetQueryCandidates(search, target.query || "");
|
|
6140
|
+
queryResolution = resolved.resolved_by;
|
|
6141
|
+
const memoryIds = resolved.memory_ids;
|
|
5661
6142
|
if (memoryIds.length === 0) {
|
|
5662
6143
|
const payload2 = {
|
|
5663
6144
|
status: "completed",
|
|
5664
6145
|
affected_ids: affectedIds,
|
|
5665
|
-
warning: "Query did not resolve to
|
|
6146
|
+
warning: resolved.warning || "Query did not resolve to a reliable memory match. No memories were changed.",
|
|
6147
|
+
resolved_by: resolved.resolved_by
|
|
5666
6148
|
};
|
|
5667
6149
|
return { content: [{ type: "text", text: JSON.stringify(payload2, null, 2) }] };
|
|
5668
6150
|
}
|
|
@@ -5684,6 +6166,7 @@ server.tool(
|
|
|
5684
6166
|
const payload = {
|
|
5685
6167
|
status: "completed",
|
|
5686
6168
|
affected_ids: affectedIds,
|
|
6169
|
+
...queryResolution ? { resolved_by: queryResolution } : {},
|
|
5687
6170
|
audit: {
|
|
5688
6171
|
audit_id: audit.audit_id,
|
|
5689
6172
|
actor: audit.actor,
|
|
@@ -5978,30 +6461,45 @@ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next"
|
|
|
5978
6461
|
var CODE_EXTENSIONS = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "py", "go", "rs", "java", "cpp", "c", "cs", "rb", "php", "swift", "kt", "sql", "prisma", "graphql", "json", "yaml", "yml", "toml", "env"]);
|
|
5979
6462
|
function extractSignature(filePath, content) {
|
|
5980
6463
|
const lines = content.split("\n");
|
|
5981
|
-
const signature = [`// File: ${filePath}`];
|
|
6464
|
+
const signature = /* @__PURE__ */ new Set([`// File: ${filePath}`]);
|
|
6465
|
+
for (const segment of filePath.split(/[\\/._-]+/).filter(Boolean)) {
|
|
6466
|
+
signature.add(`path:${segment}`);
|
|
6467
|
+
}
|
|
5982
6468
|
const head = lines.slice(0, 60);
|
|
5983
6469
|
for (const line of head) {
|
|
5984
6470
|
const trimmed = line.trim();
|
|
5985
6471
|
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*")) continue;
|
|
6472
|
+
const urlMatches = trimmed.match(/https?:\/\/[^\s"'`]+/g);
|
|
6473
|
+
if (urlMatches) {
|
|
6474
|
+
for (const match of urlMatches.slice(0, 2)) signature.add(`url:${match.slice(0, 120)}`);
|
|
6475
|
+
}
|
|
6476
|
+
const routeMatches = trimmed.match(/\/[A-Za-z0-9._~!$&'()*+,;=:@%/-]{2,}/g);
|
|
6477
|
+
if (routeMatches) {
|
|
6478
|
+
for (const match of routeMatches.slice(0, 3)) signature.add(`route:${match.slice(0, 120)}`);
|
|
6479
|
+
}
|
|
5986
6480
|
if (/^(import|from|require|use |pub use )/.test(trimmed)) {
|
|
5987
|
-
signature.
|
|
6481
|
+
signature.add(trimmed.slice(0, 120));
|
|
5988
6482
|
continue;
|
|
5989
6483
|
}
|
|
5990
6484
|
if (/^(export|async function|function|class|interface|type |const |let |def |pub fn |fn |struct |impl |enum )/.test(trimmed)) {
|
|
5991
|
-
signature.
|
|
6485
|
+
signature.add(trimmed.slice(0, 120));
|
|
5992
6486
|
continue;
|
|
5993
6487
|
}
|
|
5994
6488
|
if (trimmed.startsWith("@") || trimmed.startsWith("#[")) {
|
|
5995
|
-
signature.
|
|
6489
|
+
signature.add(trimmed.slice(0, 80));
|
|
5996
6490
|
}
|
|
5997
6491
|
}
|
|
5998
6492
|
for (const line of lines.slice(60)) {
|
|
5999
6493
|
const trimmed = line.trim();
|
|
6000
6494
|
if (/^(export (default |async )?function|export (default )?class|export const|export type|export interface|async function|function |class |def |pub fn |fn )/.test(trimmed)) {
|
|
6001
|
-
signature.
|
|
6495
|
+
signature.add(trimmed.slice(0, 120));
|
|
6496
|
+
continue;
|
|
6497
|
+
}
|
|
6498
|
+
if (/^[A-Za-z0-9_$]+\s*[:=]\s*["'`][^"'`]{3,}["'`]/.test(trimmed)) {
|
|
6499
|
+
signature.add(trimmed.slice(0, 120));
|
|
6002
6500
|
}
|
|
6003
6501
|
}
|
|
6004
|
-
return signature.join("\n").slice(0,
|
|
6502
|
+
return Array.from(signature).join("\n").slice(0, 2500);
|
|
6005
6503
|
}
|
|
6006
6504
|
server.tool(
|
|
6007
6505
|
"code.search_semantic",
|
|
@@ -6015,91 +6513,21 @@ server.tool(
|
|
|
6015
6513
|
max_files: z.number().optional().default(150).describe("Max files to scan. For large codebases, narrow with file_types instead of raising this.")
|
|
6016
6514
|
},
|
|
6017
6515
|
async ({ query, path: searchPath, file_types, top_k, threshold, max_files }) => {
|
|
6018
|
-
const rootPath = searchPath || process.cwd();
|
|
6019
|
-
const allowedExts = file_types ? new Set(file_types) : CODE_EXTENSIONS;
|
|
6020
|
-
const files = [];
|
|
6021
|
-
function collect(dir) {
|
|
6022
|
-
if (files.length >= (max_files ?? 300)) return;
|
|
6023
|
-
let entries;
|
|
6024
|
-
try {
|
|
6025
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
6026
|
-
} catch {
|
|
6027
|
-
return;
|
|
6028
|
-
}
|
|
6029
|
-
for (const entry of entries) {
|
|
6030
|
-
if (files.length >= (max_files ?? 300)) break;
|
|
6031
|
-
if (SKIP_DIRS.has(entry.name)) continue;
|
|
6032
|
-
const full = join(dir, entry.name);
|
|
6033
|
-
if (entry.isDirectory()) {
|
|
6034
|
-
collect(full);
|
|
6035
|
-
} else if (entry.isFile()) {
|
|
6036
|
-
const ext = extname(entry.name).replace(".", "");
|
|
6037
|
-
if (allowedExts.has(ext)) files.push(full);
|
|
6038
|
-
}
|
|
6039
|
-
}
|
|
6040
|
-
}
|
|
6041
|
-
collect(rootPath);
|
|
6042
|
-
if (files.length === 0) {
|
|
6043
|
-
return { content: [{ type: "text", text: `No code files found in ${rootPath}` }] };
|
|
6044
|
-
}
|
|
6045
|
-
const documents = [];
|
|
6046
|
-
for (const filePath of files) {
|
|
6047
|
-
try {
|
|
6048
|
-
const stat = statSync(filePath);
|
|
6049
|
-
if (stat.size > 500 * 1024) continue;
|
|
6050
|
-
const content = readFileSync(filePath, "utf-8");
|
|
6051
|
-
const relPath = relative(rootPath, filePath);
|
|
6052
|
-
const signature = extractSignature(relPath, content);
|
|
6053
|
-
documents.push({ id: relPath, content: signature });
|
|
6054
|
-
} catch {
|
|
6055
|
-
}
|
|
6056
|
-
}
|
|
6057
|
-
if (documents.length === 0) {
|
|
6058
|
-
return { content: [{ type: "text", text: "Could not read any files." }] };
|
|
6059
|
-
}
|
|
6060
|
-
let response;
|
|
6061
6516
|
try {
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6517
|
+
return primaryToolSuccess({
|
|
6518
|
+
...await runSearchCodeTool({
|
|
6519
|
+
query,
|
|
6520
|
+
path: searchPath,
|
|
6521
|
+
file_types,
|
|
6522
|
+
top_k,
|
|
6523
|
+
threshold,
|
|
6524
|
+
max_files
|
|
6525
|
+
}),
|
|
6526
|
+
tool: "code.search_semantic"
|
|
6067
6527
|
});
|
|
6068
6528
|
} catch (error) {
|
|
6069
|
-
return
|
|
6070
|
-
}
|
|
6071
|
-
if (!response.results || response.results.length === 0) {
|
|
6072
|
-
return { content: [{ type: "text", text: `No semantically relevant files found for: "${query}"
|
|
6073
|
-
|
|
6074
|
-
Searched ${documents.length} files in ${rootPath}.
|
|
6075
|
-
|
|
6076
|
-
Try lowering the threshold or rephrasing your query.` }] };
|
|
6077
|
-
}
|
|
6078
|
-
const lines = [
|
|
6079
|
-
`Semantic search: "${query}"`,
|
|
6080
|
-
`Searched ${documents.length} files \u2192 ${response.results.length} relevant (${response.latency_ms}ms)
|
|
6081
|
-
`
|
|
6082
|
-
];
|
|
6083
|
-
for (const result of response.results) {
|
|
6084
|
-
lines.push(`\u{1F4C4} ${result.id} (score: ${result.score})`);
|
|
6085
|
-
if (result.snippet) {
|
|
6086
|
-
lines.push(` ${result.snippet}`);
|
|
6087
|
-
}
|
|
6088
|
-
if (result.score > 0.5) {
|
|
6089
|
-
try {
|
|
6090
|
-
const fullPath = join(rootPath, result.id);
|
|
6091
|
-
const content = readFileSync(fullPath, "utf-8");
|
|
6092
|
-
const excerpt = content.split("\n").slice(0, 30).join("\n");
|
|
6093
|
-
lines.push(`
|
|
6094
|
-
\`\`\`
|
|
6095
|
-
${excerpt}
|
|
6096
|
-
\`\`\``);
|
|
6097
|
-
} catch {
|
|
6098
|
-
}
|
|
6099
|
-
}
|
|
6100
|
-
lines.push("");
|
|
6529
|
+
return primaryToolError(`Semantic search failed: ${error.message}`);
|
|
6101
6530
|
}
|
|
6102
|
-
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
6103
6531
|
}
|
|
6104
6532
|
);
|
|
6105
6533
|
function* walkDir(dir, fileTypes) {
|
|
@@ -6416,72 +6844,15 @@ server.tool(
|
|
|
6416
6844
|
max_files: z.number().optional().default(150)
|
|
6417
6845
|
},
|
|
6418
6846
|
async ({ query, path, file_types, top_k, threshold, max_files }) => {
|
|
6419
|
-
const rootPath = path || process.cwd();
|
|
6420
|
-
const allowedExts = file_types ? new Set(file_types) : CODE_EXTENSIONS;
|
|
6421
|
-
const files = [];
|
|
6422
|
-
function collect(dir) {
|
|
6423
|
-
if (files.length >= (max_files ?? 150)) return;
|
|
6424
|
-
let entries;
|
|
6425
|
-
try {
|
|
6426
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
6427
|
-
} catch {
|
|
6428
|
-
return;
|
|
6429
|
-
}
|
|
6430
|
-
for (const entry of entries) {
|
|
6431
|
-
if (files.length >= (max_files ?? 150)) break;
|
|
6432
|
-
if (SKIP_DIRS.has(entry.name)) continue;
|
|
6433
|
-
const full = join(dir, entry.name);
|
|
6434
|
-
if (entry.isDirectory()) collect(full);
|
|
6435
|
-
else if (entry.isFile()) {
|
|
6436
|
-
const ext = extname(entry.name).replace(".", "");
|
|
6437
|
-
if (allowedExts.has(ext)) files.push(full);
|
|
6438
|
-
}
|
|
6439
|
-
}
|
|
6440
|
-
}
|
|
6441
|
-
collect(rootPath);
|
|
6442
|
-
if (files.length === 0) {
|
|
6443
|
-
return primaryToolSuccess({
|
|
6444
|
-
tool: "search_code",
|
|
6445
|
-
query,
|
|
6446
|
-
path: rootPath,
|
|
6447
|
-
results: [],
|
|
6448
|
-
count: 0
|
|
6449
|
-
});
|
|
6450
|
-
}
|
|
6451
|
-
const documents = [];
|
|
6452
|
-
for (const filePath of files) {
|
|
6453
|
-
try {
|
|
6454
|
-
const stat = statSync(filePath);
|
|
6455
|
-
if (stat.size > 500 * 1024) continue;
|
|
6456
|
-
const content = readFileSync(filePath, "utf-8");
|
|
6457
|
-
const relPath = relative(rootPath, filePath);
|
|
6458
|
-
documents.push({ id: relPath, content: extractSignature(relPath, content) });
|
|
6459
|
-
} catch {
|
|
6460
|
-
}
|
|
6461
|
-
}
|
|
6462
6847
|
try {
|
|
6463
|
-
|
|
6848
|
+
return primaryToolSuccess(await runSearchCodeTool({
|
|
6464
6849
|
query,
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
tool: "search_code",
|
|
6472
|
-
query,
|
|
6473
|
-
path: rootPath,
|
|
6474
|
-
results: [],
|
|
6475
|
-
count: 0
|
|
6476
|
-
});
|
|
6477
|
-
}
|
|
6478
|
-
return primaryToolSuccess({
|
|
6479
|
-
tool: "search_code",
|
|
6480
|
-
query,
|
|
6481
|
-
path: rootPath,
|
|
6482
|
-
results: response.results,
|
|
6483
|
-
count: response.results.length
|
|
6484
|
-
});
|
|
6850
|
+
path,
|
|
6851
|
+
file_types,
|
|
6852
|
+
top_k,
|
|
6853
|
+
threshold,
|
|
6854
|
+
max_files
|
|
6855
|
+
}));
|
|
6485
6856
|
} catch (error) {
|
|
6486
6857
|
return primaryToolError(`Semantic search failed: ${error.message}`);
|
|
6487
6858
|
}
|
|
@@ -6916,8 +7287,14 @@ if (process.argv[1] && /server\.(mjs|cjs|js|ts)$/.test(process.argv[1])) {
|
|
|
6916
7287
|
main().catch(console.error);
|
|
6917
7288
|
}
|
|
6918
7289
|
export {
|
|
7290
|
+
canonicalizeWorkspacePath,
|
|
7291
|
+
chooseWorkspaceProjectSource,
|
|
7292
|
+
classifyWorkspaceHealth,
|
|
6919
7293
|
createMcpServer,
|
|
6920
7294
|
createWhisperMcpClient,
|
|
6921
7295
|
createWhisperMcpRuntimeClient,
|
|
6922
|
-
|
|
7296
|
+
extractSignature,
|
|
7297
|
+
renderScopedMcpConfig,
|
|
7298
|
+
resolveForgetQueryCandidates,
|
|
7299
|
+
runSearchCodeSearch
|
|
6923
7300
|
};
|