@usewhisper/mcp-server 2.9.0 → 2.11.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 +1250 -296
- 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,17 +3879,195 @@ 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
|
+
}
|
|
3917
|
+
function resolveWorkspaceIdentity(args) {
|
|
3918
|
+
const rootPath = canonicalizeWorkspacePath(args?.path, args?.cwd);
|
|
3919
|
+
const derivedWorkspaceId = getWorkspaceIdForPath(rootPath);
|
|
3920
|
+
const requestedWorkspaceId = args?.workspace_id?.trim();
|
|
3921
|
+
if (requestedWorkspaceId && requestedWorkspaceId !== derivedWorkspaceId) {
|
|
3922
|
+
throw new Error(
|
|
3923
|
+
`workspace_id '${requestedWorkspaceId}' does not match canonical workspace '${derivedWorkspaceId}' for ${rootPath}.`
|
|
3924
|
+
);
|
|
3925
|
+
}
|
|
3926
|
+
return {
|
|
3927
|
+
workspace_id: derivedWorkspaceId,
|
|
3928
|
+
root_path: rootPath,
|
|
3929
|
+
identity_source: args?.path?.trim() ? "path_canonical" : "cwd_canonical"
|
|
3930
|
+
};
|
|
3931
|
+
}
|
|
3882
3932
|
function getWorkspaceId(workspaceId) {
|
|
3883
3933
|
if (workspaceId?.trim()) return workspaceId.trim();
|
|
3884
|
-
const seed = `${process.cwd()}|${
|
|
3934
|
+
const seed = `${canonicalizeWorkspacePath(process.cwd())}|${API_KEY.slice(0, 12) || "anon"}`;
|
|
3885
3935
|
return createHash("sha256").update(seed).digest("hex").slice(0, 20);
|
|
3886
3936
|
}
|
|
3887
3937
|
function getWorkspaceIdForPath(path, workspaceId) {
|
|
3888
3938
|
if (workspaceId?.trim()) return workspaceId.trim();
|
|
3889
3939
|
if (!path) return getWorkspaceId(void 0);
|
|
3890
|
-
const seed = `${path}|${
|
|
3940
|
+
const seed = `${canonicalizeWorkspacePath(path)}|${API_KEY.slice(0, 12) || "anon"}`;
|
|
3891
3941
|
return createHash("sha256").update(seed).digest("hex").slice(0, 20);
|
|
3892
3942
|
}
|
|
3943
|
+
function normalizeLoosePath(value) {
|
|
3944
|
+
if (!value || !String(value).trim()) return null;
|
|
3945
|
+
return canonicalizeWorkspacePath(String(value));
|
|
3946
|
+
}
|
|
3947
|
+
function normalizeRepoName(value) {
|
|
3948
|
+
if (!value || !String(value).trim()) return null;
|
|
3949
|
+
return String(value).trim().replace(/\.git$/i, "").toLowerCase();
|
|
3950
|
+
}
|
|
3951
|
+
function parseGitHubRemote(remote) {
|
|
3952
|
+
if (!remote) return null;
|
|
3953
|
+
const normalized = remote.trim().replace(/\.git$/i, "");
|
|
3954
|
+
const httpsMatch = normalized.match(/github\.com[/:]([^/:\s]+)\/([^/\s]+)$/i);
|
|
3955
|
+
if (httpsMatch) {
|
|
3956
|
+
return { owner: httpsMatch[1].toLowerCase(), repo: httpsMatch[2].toLowerCase() };
|
|
3957
|
+
}
|
|
3958
|
+
const sshMatch = normalized.match(/git@github\.com:([^/:\s]+)\/([^/\s]+)$/i);
|
|
3959
|
+
if (sshMatch) {
|
|
3960
|
+
return { owner: sshMatch[1].toLowerCase(), repo: sshMatch[2].toLowerCase() };
|
|
3961
|
+
}
|
|
3962
|
+
return null;
|
|
3963
|
+
}
|
|
3964
|
+
function getGitRemoteUrl(searchPath) {
|
|
3965
|
+
const root = searchPath || process.cwd();
|
|
3966
|
+
const result = spawnSync("git", ["-C", root, "config", "--get", "remote.origin.url"], { encoding: "utf-8" });
|
|
3967
|
+
if (result.status !== 0) return void 0;
|
|
3968
|
+
const out = String(result.stdout || "").trim();
|
|
3969
|
+
return out || void 0;
|
|
3970
|
+
}
|
|
3971
|
+
function getGitBranch(searchPath) {
|
|
3972
|
+
const root = searchPath || process.cwd();
|
|
3973
|
+
const result = spawnSync("git", ["-C", root, "rev-parse", "--abbrev-ref", "HEAD"], { encoding: "utf-8" });
|
|
3974
|
+
if (result.status !== 0) return void 0;
|
|
3975
|
+
const out = String(result.stdout || "").trim();
|
|
3976
|
+
return out || void 0;
|
|
3977
|
+
}
|
|
3978
|
+
function sourceStatusLooksReady(status) {
|
|
3979
|
+
const normalized = String(status || "").trim().toLowerCase();
|
|
3980
|
+
if (!normalized) return true;
|
|
3981
|
+
if (["ready", "indexed", "completed", "complete", "active", "synced", "success"].includes(normalized)) return true;
|
|
3982
|
+
if (["processing", "queued", "syncing", "pending", "creating", "indexing", "error", "failed"].includes(normalized)) {
|
|
3983
|
+
return false;
|
|
3984
|
+
}
|
|
3985
|
+
return !normalized.includes("error") && !normalized.includes("fail") && !normalized.includes("queue");
|
|
3986
|
+
}
|
|
3987
|
+
function sourceMatchesWorkspacePath(source, rootPath) {
|
|
3988
|
+
const config = source.config || {};
|
|
3989
|
+
const candidates = [
|
|
3990
|
+
config.path,
|
|
3991
|
+
config.root_path,
|
|
3992
|
+
config.workspace_path,
|
|
3993
|
+
config.workspacePath,
|
|
3994
|
+
config.local_path,
|
|
3995
|
+
config.file_path,
|
|
3996
|
+
config.directory
|
|
3997
|
+
].map((value) => normalizeLoosePath(typeof value === "string" ? value : null)).filter(Boolean);
|
|
3998
|
+
return candidates.some((candidate) => candidate === rootPath || rootPath.startsWith(`${candidate}/`) || candidate.startsWith(`${rootPath}/`));
|
|
3999
|
+
}
|
|
4000
|
+
function sourceMatchesWorkspaceRepo(source, repoIdentity, branch) {
|
|
4001
|
+
if (!repoIdentity) return false;
|
|
4002
|
+
const config = source.config || {};
|
|
4003
|
+
const owner = normalizeRepoName(config.owner || config.org || config.organization || null);
|
|
4004
|
+
const repo = normalizeRepoName(config.repo || config.repository || config.name || null);
|
|
4005
|
+
const sourceUrlRepo = parseGitHubRemote(typeof config.url === "string" ? config.url : null);
|
|
4006
|
+
const branchValue = normalizeRepoName(config.branch || config.ref || config.default_branch || null);
|
|
4007
|
+
const ownerMatches = owner === repoIdentity.owner || sourceUrlRepo?.owner === repoIdentity.owner;
|
|
4008
|
+
const repoMatches = repo === repoIdentity.repo || sourceUrlRepo?.repo === repoIdentity.repo;
|
|
4009
|
+
if (!ownerMatches || !repoMatches) return false;
|
|
4010
|
+
if (!branchValue || !branch) return true;
|
|
4011
|
+
return branchValue === normalizeRepoName(branch);
|
|
4012
|
+
}
|
|
4013
|
+
function classifyProjectRepoReadiness(args) {
|
|
4014
|
+
const remoteIdentity = parseGitHubRemote(args.remote_url || null);
|
|
4015
|
+
const matchedSources = args.sources.filter(
|
|
4016
|
+
(source) => sourceMatchesWorkspacePath(source, args.root_path) || sourceMatchesWorkspaceRepo(source, remoteIdentity, args.branch || void 0)
|
|
4017
|
+
);
|
|
4018
|
+
if (matchedSources.length === 0) {
|
|
4019
|
+
return {
|
|
4020
|
+
retrieval_readiness: "project_bound_no_repo_source",
|
|
4021
|
+
matched_sources: [],
|
|
4022
|
+
matched_source_ids: [],
|
|
4023
|
+
warnings: [`No verified local or GitHub source matches ${args.root_path}.`]
|
|
4024
|
+
};
|
|
4025
|
+
}
|
|
4026
|
+
const readySources = matchedSources.filter((source) => sourceStatusLooksReady(source.status));
|
|
4027
|
+
if (readySources.length === 0) {
|
|
4028
|
+
return {
|
|
4029
|
+
retrieval_readiness: "project_repo_source_stale",
|
|
4030
|
+
matched_sources: matchedSources,
|
|
4031
|
+
matched_source_ids: matchedSources.map((source) => source.id),
|
|
4032
|
+
warnings: [
|
|
4033
|
+
`Matching repo sources are present but not ready (${matchedSources.map((source) => `${source.name}:${source.status}`).join(", ")}).`
|
|
4034
|
+
]
|
|
4035
|
+
};
|
|
4036
|
+
}
|
|
4037
|
+
return {
|
|
4038
|
+
retrieval_readiness: "project_repo_source_ready",
|
|
4039
|
+
matched_sources: readySources,
|
|
4040
|
+
matched_source_ids: readySources.map((source) => source.id),
|
|
4041
|
+
warnings: []
|
|
4042
|
+
};
|
|
4043
|
+
}
|
|
4044
|
+
function classifyRepoGroundedQuery(args) {
|
|
4045
|
+
const query = args.query.toLowerCase();
|
|
4046
|
+
let score = 0;
|
|
4047
|
+
if (/(^|[\s"'])where is\b|which file|what file|show me|wiring|handler|middleware|implementation|route|endpoint|function|class|module|repo|workspace|code/.test(query)) {
|
|
4048
|
+
score += 1;
|
|
4049
|
+
}
|
|
4050
|
+
if (/\bauth\b|\burl\b|\bcookie\b|\bsession\b|\bapi\b/.test(query)) {
|
|
4051
|
+
score += 1;
|
|
4052
|
+
}
|
|
4053
|
+
if (/[A-Za-z0-9/_-]+\.[A-Za-z0-9]+/.test(args.query) || /\/[A-Za-z0-9._~!$&'()*+,;=:@%/-]{2,}/.test(args.query)) {
|
|
4054
|
+
score += 2;
|
|
4055
|
+
}
|
|
4056
|
+
if (/[A-Za-z_$][A-Za-z0-9_$-]*\(/.test(args.query) || /[A-Z][A-Za-z0-9]+/.test(args.query)) {
|
|
4057
|
+
score += 1;
|
|
4058
|
+
}
|
|
4059
|
+
if ((args.chunk_types || []).some((chunkType) => ["code", "function", "class", "config", "schema", "api_spec"].includes(chunkType))) {
|
|
4060
|
+
score += 2;
|
|
4061
|
+
}
|
|
4062
|
+
const changedTokens = new Set((args.changed_path_tokens || []).map((token) => token.toLowerCase()));
|
|
4063
|
+
if (changedTokens.size > 0) {
|
|
4064
|
+
const queryTokens = tokenizeQueryForLexicalRescue(args.query);
|
|
4065
|
+
if (queryTokens.some((token) => changedTokens.has(token.toLowerCase()))) {
|
|
4066
|
+
score += 1;
|
|
4067
|
+
}
|
|
4068
|
+
}
|
|
4069
|
+
return score >= 2;
|
|
4070
|
+
}
|
|
3893
4071
|
function clamp012(value) {
|
|
3894
4072
|
if (Number.isNaN(value)) return 0;
|
|
3895
4073
|
if (value < 0) return 0;
|
|
@@ -3978,7 +4156,10 @@ function getWorkspaceState(state, workspaceId) {
|
|
|
3978
4156
|
annotations: [],
|
|
3979
4157
|
session_summaries: [],
|
|
3980
4158
|
events: [],
|
|
3981
|
-
index_metadata: {}
|
|
4159
|
+
index_metadata: {},
|
|
4160
|
+
root_path: void 0,
|
|
4161
|
+
project_ref: void 0,
|
|
4162
|
+
project_id: void 0
|
|
3982
4163
|
};
|
|
3983
4164
|
}
|
|
3984
4165
|
return state.workspaces[workspaceId];
|
|
@@ -3988,17 +4169,126 @@ function computeChecksum(value) {
|
|
|
3988
4169
|
}
|
|
3989
4170
|
var cachedProjectRef = DEFAULT_PROJECT || void 0;
|
|
3990
4171
|
var cachedMcpSessionId = process.env.WHISPER_SESSION_ID || `mcp_${randomUUID().slice(0, 12)}`;
|
|
4172
|
+
async function resolveProjectDescriptor(projectRef) {
|
|
4173
|
+
try {
|
|
4174
|
+
const resolved = await whisper.resolveProject(projectRef);
|
|
4175
|
+
const resolvedRef = resolved.slug || resolved.name || resolved.id || projectRef;
|
|
4176
|
+
cachedProjectRef = resolvedRef;
|
|
4177
|
+
return { project_ref: resolvedRef, project_id: resolved.id || null };
|
|
4178
|
+
} catch {
|
|
4179
|
+
cachedProjectRef = projectRef;
|
|
4180
|
+
return { project_ref: projectRef, project_id: null };
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
4183
|
+
function getWorkspaceRecommendedNextCalls(health) {
|
|
4184
|
+
if (health === "unbound") return ["index.workspace_resolve", "context.list_projects", "index.workspace_status"];
|
|
4185
|
+
if (health === "unindexed") return ["index.workspace_status", "index.workspace_run", "search_code", "grep"];
|
|
4186
|
+
if (health === "stale" || health === "drifted") return ["index.workspace_status", "index.workspace_run", "grep", "search_code"];
|
|
4187
|
+
return [];
|
|
4188
|
+
}
|
|
4189
|
+
function getWorkspaceWarnings(args) {
|
|
4190
|
+
if (args.health === "unbound") {
|
|
4191
|
+
return [`Workspace ${args.rootPath} is not bound to a Whisper project.`];
|
|
4192
|
+
}
|
|
4193
|
+
if (args.health === "unindexed") {
|
|
4194
|
+
return [`Workspace ${args.rootPath} is bound to ${args.projectRef}, but no usable local index metadata exists yet.`];
|
|
4195
|
+
}
|
|
4196
|
+
if (args.health === "stale") {
|
|
4197
|
+
const age = args.freshness.age_hours == null ? "unknown" : `${Math.round(args.freshness.age_hours)}h`;
|
|
4198
|
+
return [`Workspace index for ${args.projectRef} is stale (${age} old).`];
|
|
4199
|
+
}
|
|
4200
|
+
if (args.health === "drifted") {
|
|
4201
|
+
const reasons = [];
|
|
4202
|
+
if (args.pendingChanges && args.pendingChanges > 0) reasons.push(`${args.pendingChanges} pending local changes`);
|
|
4203
|
+
if (args.lastIndexedCommit && args.currentCommit && args.lastIndexedCommit !== args.currentCommit) {
|
|
4204
|
+
reasons.push(`indexed commit ${args.lastIndexedCommit.slice(0, 12)} differs from current ${args.currentCommit.slice(0, 12)}`);
|
|
4205
|
+
}
|
|
4206
|
+
const suffix = reasons.length ? `: ${reasons.join("; ")}` : "";
|
|
4207
|
+
return [`Workspace state drifted since the last index${suffix}.`];
|
|
4208
|
+
}
|
|
4209
|
+
return [];
|
|
4210
|
+
}
|
|
4211
|
+
async function resolveWorkspaceTrust(args) {
|
|
4212
|
+
const identity = resolveWorkspaceIdentity({ path: args.path, workspace_id: args.workspace_id });
|
|
4213
|
+
const rootPath = identity.root_path;
|
|
4214
|
+
const workspaceId = identity.workspace_id;
|
|
4215
|
+
const state = loadState();
|
|
4216
|
+
const workspace = getWorkspaceState(state, workspaceId);
|
|
4217
|
+
let mutated = false;
|
|
4218
|
+
if (workspace.root_path !== rootPath) {
|
|
4219
|
+
workspace.root_path = rootPath;
|
|
4220
|
+
mutated = true;
|
|
4221
|
+
}
|
|
4222
|
+
const selected = chooseWorkspaceProjectSource({
|
|
4223
|
+
explicitProject: args.project,
|
|
4224
|
+
workspaceProjectRef: workspace.project_ref,
|
|
4225
|
+
defaultProject: DEFAULT_PROJECT || null
|
|
4226
|
+
});
|
|
4227
|
+
let projectRef = null;
|
|
4228
|
+
let projectId = workspace.project_id || null;
|
|
4229
|
+
if (selected.project_ref) {
|
|
4230
|
+
const resolved = await resolveProjectDescriptor(selected.project_ref);
|
|
4231
|
+
projectRef = resolved.project_ref;
|
|
4232
|
+
projectId = resolved.project_id;
|
|
4233
|
+
if (workspace.project_ref !== projectRef || (workspace.project_id || null) !== projectId) {
|
|
4234
|
+
workspace.project_ref = projectRef;
|
|
4235
|
+
workspace.project_id = projectId || void 0;
|
|
4236
|
+
mutated = true;
|
|
4237
|
+
}
|
|
4238
|
+
}
|
|
4239
|
+
if (mutated) saveState(state);
|
|
4240
|
+
const lastIndexedAt = workspace.index_metadata?.last_indexed_at || null;
|
|
4241
|
+
const ageHours = lastIndexedAt ? (Date.now() - new Date(lastIndexedAt).getTime()) / (60 * 60 * 1e3) : null;
|
|
4242
|
+
const currentCommit = getGitHead(rootPath) || null;
|
|
4243
|
+
const pendingChanges = getGitPendingCount(rootPath);
|
|
4244
|
+
const lastIndexedCommit = workspace.index_metadata?.last_indexed_commit || null;
|
|
4245
|
+
const coverage = workspace.index_metadata?.coverage ?? 0;
|
|
4246
|
+
const health = classifyWorkspaceHealth({
|
|
4247
|
+
project_ref: projectRef,
|
|
4248
|
+
coverage,
|
|
4249
|
+
last_indexed_at: lastIndexedAt,
|
|
4250
|
+
last_indexed_commit: lastIndexedCommit,
|
|
4251
|
+
current_commit: currentCommit,
|
|
4252
|
+
pending_changes: pendingChanges ?? null,
|
|
4253
|
+
max_staleness_hours: args.max_staleness_hours ?? 168
|
|
4254
|
+
});
|
|
4255
|
+
const freshness = {
|
|
4256
|
+
stale: health === "stale",
|
|
4257
|
+
age_hours: ageHours,
|
|
4258
|
+
last_indexed_at: lastIndexedAt
|
|
4259
|
+
};
|
|
4260
|
+
const warnings = getWorkspaceWarnings({
|
|
4261
|
+
health,
|
|
4262
|
+
rootPath,
|
|
4263
|
+
projectRef,
|
|
4264
|
+
currentCommit,
|
|
4265
|
+
lastIndexedCommit,
|
|
4266
|
+
pendingChanges: pendingChanges ?? null,
|
|
4267
|
+
freshness
|
|
4268
|
+
});
|
|
4269
|
+
return {
|
|
4270
|
+
workspace_id: workspaceId,
|
|
4271
|
+
root_path: rootPath,
|
|
4272
|
+
identity_source: identity.identity_source,
|
|
4273
|
+
project_ref: projectRef,
|
|
4274
|
+
project_id: projectId,
|
|
4275
|
+
resolved_by: selected.resolved_by,
|
|
4276
|
+
health,
|
|
4277
|
+
warnings,
|
|
4278
|
+
recommended_next_calls: getWorkspaceRecommendedNextCalls(health),
|
|
4279
|
+
freshness,
|
|
4280
|
+
coverage,
|
|
4281
|
+
last_indexed_commit: lastIndexedCommit,
|
|
4282
|
+
current_commit: currentCommit,
|
|
4283
|
+
pending_changes: pendingChanges ?? null,
|
|
4284
|
+
grounded_to_workspace: health === "healthy"
|
|
4285
|
+
};
|
|
4286
|
+
}
|
|
3991
4287
|
async function resolveProjectRef(explicit) {
|
|
3992
4288
|
if (explicit?.trim()) {
|
|
3993
4289
|
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
|
-
}
|
|
4290
|
+
const resolved = await resolveProjectDescriptor(requestedRef);
|
|
4291
|
+
return resolved.project_ref;
|
|
4002
4292
|
}
|
|
4003
4293
|
if (cachedProjectRef) return cachedProjectRef;
|
|
4004
4294
|
try {
|
|
@@ -4011,6 +4301,98 @@ async function resolveProjectRef(explicit) {
|
|
|
4011
4301
|
return void 0;
|
|
4012
4302
|
}
|
|
4013
4303
|
}
|
|
4304
|
+
function collectGitChangedPathTokens(searchPath) {
|
|
4305
|
+
const root = searchPath || process.cwd();
|
|
4306
|
+
const result = spawnSync("git", ["-C", root, "status", "--porcelain"], { encoding: "utf-8" });
|
|
4307
|
+
if (result.status !== 0) return [];
|
|
4308
|
+
const lines = String(result.stdout || "").split("\n").map((line) => line.trim()).filter(Boolean);
|
|
4309
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
4310
|
+
for (const line of lines) {
|
|
4311
|
+
const filePath = line.slice(3).split(" -> ").at(-1)?.trim();
|
|
4312
|
+
if (!filePath) continue;
|
|
4313
|
+
for (const token of filePath.split(/[\\/._-]+/).filter((part) => part.length >= 3)) {
|
|
4314
|
+
tokens.add(token.toLowerCase());
|
|
4315
|
+
}
|
|
4316
|
+
}
|
|
4317
|
+
return Array.from(tokens);
|
|
4318
|
+
}
|
|
4319
|
+
async function inspectProjectRepoSources(args) {
|
|
4320
|
+
if (!args.project_ref) {
|
|
4321
|
+
return {
|
|
4322
|
+
retrieval_readiness: "no_project",
|
|
4323
|
+
matched_sources: [],
|
|
4324
|
+
matched_source_ids: [],
|
|
4325
|
+
warnings: ["No Whisper project is bound to this workspace."]
|
|
4326
|
+
};
|
|
4327
|
+
}
|
|
4328
|
+
try {
|
|
4329
|
+
const sourceData = await whisper.listSources(args.project_ref);
|
|
4330
|
+
const classified = classifyProjectRepoReadiness({
|
|
4331
|
+
sources: sourceData.sources || [],
|
|
4332
|
+
root_path: args.root_path,
|
|
4333
|
+
remote_url: getGitRemoteUrl(args.root_path) || null,
|
|
4334
|
+
branch: getGitBranch(args.root_path) || null
|
|
4335
|
+
});
|
|
4336
|
+
if (classified.retrieval_readiness === "project_bound_no_repo_source") {
|
|
4337
|
+
return {
|
|
4338
|
+
...classified,
|
|
4339
|
+
warnings: [`Project ${args.project_ref} has no verified local or GitHub source for ${args.root_path}.`]
|
|
4340
|
+
};
|
|
4341
|
+
}
|
|
4342
|
+
if (classified.retrieval_readiness === "project_repo_source_stale") {
|
|
4343
|
+
return {
|
|
4344
|
+
...classified,
|
|
4345
|
+
warnings: [
|
|
4346
|
+
`Project ${args.project_ref} has matching repo sources, but none are ready (${classified.matched_sources.map((source) => `${source.name}:${source.status}`).join(", ")}).`
|
|
4347
|
+
]
|
|
4348
|
+
};
|
|
4349
|
+
}
|
|
4350
|
+
return classified;
|
|
4351
|
+
} catch (error) {
|
|
4352
|
+
return {
|
|
4353
|
+
retrieval_readiness: "project_unverified",
|
|
4354
|
+
matched_sources: [],
|
|
4355
|
+
matched_source_ids: [],
|
|
4356
|
+
warnings: [`Could not verify project sources for ${args.project_ref}: ${error.message}`]
|
|
4357
|
+
};
|
|
4358
|
+
}
|
|
4359
|
+
}
|
|
4360
|
+
async function resolveRepoGroundingPreflight(args) {
|
|
4361
|
+
const trust = await resolveWorkspaceTrust({ path: args.path, workspace_id: args.workspace_id, project: args.project });
|
|
4362
|
+
const repoGrounded = classifyRepoGroundedQuery({
|
|
4363
|
+
query: args.query,
|
|
4364
|
+
chunk_types: args.chunk_types,
|
|
4365
|
+
changed_path_tokens: collectGitChangedPathTokens(trust.root_path)
|
|
4366
|
+
});
|
|
4367
|
+
const sourceVerification = await inspectProjectRepoSources({
|
|
4368
|
+
project_ref: trust.project_ref,
|
|
4369
|
+
root_path: trust.root_path
|
|
4370
|
+
});
|
|
4371
|
+
const warnings = [...trust.warnings, ...sourceVerification.warnings];
|
|
4372
|
+
let retrievalReadiness = sourceVerification.retrieval_readiness;
|
|
4373
|
+
let retrievalRoute = "none";
|
|
4374
|
+
if (repoGrounded) {
|
|
4375
|
+
retrievalRoute = sourceVerification.retrieval_readiness === "project_repo_source_ready" ? "project_repo" : "local_workspace_fallback";
|
|
4376
|
+
if (retrievalRoute === "local_workspace_fallback") {
|
|
4377
|
+
retrievalReadiness = "local_fallback";
|
|
4378
|
+
warnings.push("Using live local workspace retrieval because project-backed repo retrieval is unavailable or unverified.");
|
|
4379
|
+
}
|
|
4380
|
+
}
|
|
4381
|
+
const recommendedNextCalls = [...trust.recommended_next_calls];
|
|
4382
|
+
if (sourceVerification.retrieval_readiness === "project_bound_no_repo_source" || sourceVerification.retrieval_readiness === "project_unverified") {
|
|
4383
|
+
recommendedNextCalls.push("context.list_sources", "index.local_scan_ingest");
|
|
4384
|
+
}
|
|
4385
|
+
return {
|
|
4386
|
+
repo_grounded: repoGrounded,
|
|
4387
|
+
trust_state: trust,
|
|
4388
|
+
retrieval_readiness: retrievalReadiness,
|
|
4389
|
+
retrieval_route: retrievalRoute,
|
|
4390
|
+
matched_sources: sourceVerification.matched_sources,
|
|
4391
|
+
matched_source_ids: sourceVerification.matched_source_ids,
|
|
4392
|
+
warnings: Array.from(new Set(warnings)),
|
|
4393
|
+
recommended_next_calls: Array.from(new Set(recommendedNextCalls))
|
|
4394
|
+
};
|
|
4395
|
+
}
|
|
4014
4396
|
async function ingestSessionWithSyncFallback(params) {
|
|
4015
4397
|
try {
|
|
4016
4398
|
return await whisper.ingestSession({
|
|
@@ -4037,7 +4419,7 @@ function resolveMcpScope(params) {
|
|
|
4037
4419
|
project: params?.project,
|
|
4038
4420
|
userId: params?.user_id?.trim() || defaultMcpUserId(),
|
|
4039
4421
|
sessionId: params?.session_id?.trim() || cachedMcpSessionId,
|
|
4040
|
-
workspacePath: process.env.WHISPER_WORKSPACE_PATH || process.cwd()
|
|
4422
|
+
workspacePath: canonicalizeWorkspacePath(params?.path || process.env.WHISPER_WORKSPACE_PATH || process.cwd())
|
|
4041
4423
|
};
|
|
4042
4424
|
}
|
|
4043
4425
|
async function prepareAutomaticQuery(params) {
|
|
@@ -4101,7 +4483,9 @@ function buildAbstain(args) {
|
|
|
4101
4483
|
reason: args.reason,
|
|
4102
4484
|
message: args.message,
|
|
4103
4485
|
closest_evidence: args.closest_evidence,
|
|
4104
|
-
|
|
4486
|
+
warnings: args.warnings || [],
|
|
4487
|
+
trust_state: args.trust_state,
|
|
4488
|
+
recommended_next_calls: args.recommended_next_calls || ["index.workspace_status", "index.workspace_run", "grep", "context.get_relevant"],
|
|
4105
4489
|
diagnostics: {
|
|
4106
4490
|
claims_evaluated: args.claims_evaluated,
|
|
4107
4491
|
evidence_items_found: args.evidence_items_found,
|
|
@@ -4177,6 +4561,86 @@ function formatCanonicalMemoryResults(rawResults) {
|
|
|
4177
4561
|
};
|
|
4178
4562
|
});
|
|
4179
4563
|
}
|
|
4564
|
+
function normalizeLooseText(value) {
|
|
4565
|
+
return String(value || "").trim().toLowerCase().replace(/\s+/g, " ");
|
|
4566
|
+
}
|
|
4567
|
+
function resolveForgetQueryCandidates(rawResults, query) {
|
|
4568
|
+
const normalizedQuery = normalizeLooseText(query);
|
|
4569
|
+
const candidates = normalizeCanonicalResults(rawResults).map((result) => {
|
|
4570
|
+
const memory = result?.memory || result;
|
|
4571
|
+
const metadata = memory?.metadata || {};
|
|
4572
|
+
return {
|
|
4573
|
+
id: String(memory?.id || "").trim(),
|
|
4574
|
+
content: normalizeLooseText(memory?.content),
|
|
4575
|
+
normalized_content: normalizeLooseText(metadata?.normalized_content),
|
|
4576
|
+
canonical_content: normalizeLooseText(metadata?.canonical_content),
|
|
4577
|
+
similarity: typeof result?.similarity === "number" ? result.similarity : typeof result?.score === "number" ? result.score : 0
|
|
4578
|
+
};
|
|
4579
|
+
}).filter((candidate) => candidate.id);
|
|
4580
|
+
const exactMatches = candidates.filter(
|
|
4581
|
+
(candidate) => candidate.id.toLowerCase() === normalizedQuery || candidate.content === normalizedQuery || candidate.normalized_content === normalizedQuery || candidate.canonical_content === normalizedQuery
|
|
4582
|
+
);
|
|
4583
|
+
if (exactMatches.length > 0) {
|
|
4584
|
+
return { memory_ids: [...new Set(exactMatches.map((candidate) => candidate.id))], resolved_by: "exact" };
|
|
4585
|
+
}
|
|
4586
|
+
const substringMatches = candidates.filter((candidate) => {
|
|
4587
|
+
const haystacks = [candidate.content, candidate.normalized_content, candidate.canonical_content].filter(Boolean);
|
|
4588
|
+
return haystacks.some(
|
|
4589
|
+
(value) => value.includes(normalizedQuery) || normalizedQuery.length >= 12 && normalizedQuery.includes(value)
|
|
4590
|
+
);
|
|
4591
|
+
});
|
|
4592
|
+
if (substringMatches.length === 1) {
|
|
4593
|
+
return { memory_ids: [substringMatches[0].id], resolved_by: "substring" };
|
|
4594
|
+
}
|
|
4595
|
+
if (substringMatches.length > 1) {
|
|
4596
|
+
return {
|
|
4597
|
+
memory_ids: [],
|
|
4598
|
+
resolved_by: "ambiguous",
|
|
4599
|
+
warning: "Query matched multiple memories by recognizable text. Use memory_id or a more specific query."
|
|
4600
|
+
};
|
|
4601
|
+
}
|
|
4602
|
+
const ranked = [...candidates].sort((a, b) => b.similarity - a.similarity);
|
|
4603
|
+
if (ranked[0] && ranked[0].similarity >= 0.9 && (!ranked[1] || ranked[0].similarity - ranked[1].similarity >= 0.08)) {
|
|
4604
|
+
return { memory_ids: [ranked[0].id], resolved_by: "high_confidence" };
|
|
4605
|
+
}
|
|
4606
|
+
return { memory_ids: [], resolved_by: "none", warning: "Query did not resolve to a reliable memory match. No memories were changed." };
|
|
4607
|
+
}
|
|
4608
|
+
async function searchMemoriesForContextQuery(args) {
|
|
4609
|
+
return whisper.searchMemoriesSOTA({
|
|
4610
|
+
project: args.project,
|
|
4611
|
+
query: args.query,
|
|
4612
|
+
user_id: args.user_id,
|
|
4613
|
+
session_id: args.session_id,
|
|
4614
|
+
top_k: args.top_k ?? 5,
|
|
4615
|
+
include_relations: false,
|
|
4616
|
+
include_pending: true
|
|
4617
|
+
});
|
|
4618
|
+
}
|
|
4619
|
+
async function runContextQueryMemoryRescue(args) {
|
|
4620
|
+
const scoped = await searchMemoriesForContextQuery(args);
|
|
4621
|
+
const scopedResults = formatCanonicalMemoryResults(scoped);
|
|
4622
|
+
if (scopedResults.length > 0) {
|
|
4623
|
+
return { results: scopedResults, rescue_mode: "scoped" };
|
|
4624
|
+
}
|
|
4625
|
+
if (args.user_id || args.session_id) {
|
|
4626
|
+
return { results: [], rescue_mode: null };
|
|
4627
|
+
}
|
|
4628
|
+
const broad = await searchMemoriesForContextQuery({
|
|
4629
|
+
project: args.project,
|
|
4630
|
+
query: args.query,
|
|
4631
|
+
top_k: args.top_k
|
|
4632
|
+
});
|
|
4633
|
+
return { results: formatCanonicalMemoryResults(broad), rescue_mode: formatCanonicalMemoryResults(broad).length > 0 ? "project_broad" : null };
|
|
4634
|
+
}
|
|
4635
|
+
function renderContextQueryMemoryRescue(args) {
|
|
4636
|
+
const lines = args.results.map(
|
|
4637
|
+
(result, index) => `${index + 1}. [${result.memory_type || "memory"}, score: ${result.similarity ?? "n/a"}] ${result.content}`
|
|
4638
|
+
);
|
|
4639
|
+
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`;
|
|
4640
|
+
return `Found ${args.results.length} memory result(s) (${scopeLabel}):
|
|
4641
|
+
|
|
4642
|
+
${lines.join("\n\n")}`;
|
|
4643
|
+
}
|
|
4180
4644
|
function likelyEmbeddingFailure(error) {
|
|
4181
4645
|
const message = String(error?.message || error || "").toLowerCase();
|
|
4182
4646
|
return message.includes("embedding") || message.includes("vector") || message.includes("timeout") || message.includes("timed out") || message.includes("temporarily unavailable");
|
|
@@ -4191,6 +4655,7 @@ async function queryWithDegradedFallback(params) {
|
|
|
4191
4655
|
include_graph: params.include_graph,
|
|
4192
4656
|
user_id: params.user_id,
|
|
4193
4657
|
session_id: params.session_id,
|
|
4658
|
+
source_ids: params.source_ids,
|
|
4194
4659
|
hybrid: true,
|
|
4195
4660
|
rerank: true
|
|
4196
4661
|
});
|
|
@@ -4205,6 +4670,7 @@ async function queryWithDegradedFallback(params) {
|
|
|
4205
4670
|
include_graph: false,
|
|
4206
4671
|
user_id: params.user_id,
|
|
4207
4672
|
session_id: params.session_id,
|
|
4673
|
+
source_ids: params.source_ids,
|
|
4208
4674
|
hybrid: false,
|
|
4209
4675
|
rerank: false,
|
|
4210
4676
|
vector_weight: 0,
|
|
@@ -4218,6 +4684,308 @@ async function queryWithDegradedFallback(params) {
|
|
|
4218
4684
|
};
|
|
4219
4685
|
}
|
|
4220
4686
|
}
|
|
4687
|
+
function collectCodeFiles(rootPath, allowedExts, maxFiles) {
|
|
4688
|
+
const files = [];
|
|
4689
|
+
function collect(dir) {
|
|
4690
|
+
if (files.length >= maxFiles) return;
|
|
4691
|
+
let entries;
|
|
4692
|
+
try {
|
|
4693
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
4694
|
+
} catch {
|
|
4695
|
+
return;
|
|
4696
|
+
}
|
|
4697
|
+
for (const entry of entries) {
|
|
4698
|
+
if (files.length >= maxFiles) break;
|
|
4699
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
4700
|
+
const full = join(dir, entry.name);
|
|
4701
|
+
if (entry.isDirectory()) collect(full);
|
|
4702
|
+
else if (entry.isFile()) {
|
|
4703
|
+
const ext = extname(entry.name).replace(".", "");
|
|
4704
|
+
if (allowedExts.has(ext)) files.push(full);
|
|
4705
|
+
}
|
|
4706
|
+
}
|
|
4707
|
+
}
|
|
4708
|
+
collect(rootPath);
|
|
4709
|
+
return files;
|
|
4710
|
+
}
|
|
4711
|
+
function tokenizeQueryForLexicalRescue(query) {
|
|
4712
|
+
const stopWords = /* @__PURE__ */ new Set(["where", "what", "show", "find", "logic", "code", "file", "files", "handled", "handling"]);
|
|
4713
|
+
const rawTokens = query.toLowerCase().split(/[^a-z0-9/._:-]+/).map((token) => token.trim()).filter(Boolean);
|
|
4714
|
+
const expanded = /* @__PURE__ */ new Set();
|
|
4715
|
+
for (const token of rawTokens) {
|
|
4716
|
+
if (token.length >= 3 && !stopWords.has(token)) expanded.add(token);
|
|
4717
|
+
for (const part of token.split(/[/:._-]+/).filter((value) => value.length >= 3 && !stopWords.has(value))) {
|
|
4718
|
+
expanded.add(part);
|
|
4719
|
+
}
|
|
4720
|
+
}
|
|
4721
|
+
return Array.from(expanded);
|
|
4722
|
+
}
|
|
4723
|
+
function buildLexicalRescueResults(args) {
|
|
4724
|
+
const tokens = tokenizeQueryForLexicalRescue(args.query);
|
|
4725
|
+
if (tokens.length === 0) return [];
|
|
4726
|
+
const scored = args.documents.map((doc) => {
|
|
4727
|
+
const haystack = `${doc.id}
|
|
4728
|
+
${doc.content}
|
|
4729
|
+
${doc.raw_content}`.toLowerCase();
|
|
4730
|
+
const tokenHits = tokens.filter((token) => haystack.includes(token));
|
|
4731
|
+
const uniqueHits = tokenHits.length;
|
|
4732
|
+
const exactPhraseBonus = haystack.includes(args.query.toLowerCase()) ? 0.25 : 0;
|
|
4733
|
+
const score = uniqueHits === 0 ? 0 : Math.min(0.95, uniqueHits / tokens.length + exactPhraseBonus);
|
|
4734
|
+
return {
|
|
4735
|
+
id: doc.id,
|
|
4736
|
+
score,
|
|
4737
|
+
content: doc.content,
|
|
4738
|
+
snippet: doc.content.split("\n").find((line) => line.trim().length > 10)?.slice(0, 200) || doc.id,
|
|
4739
|
+
search_mode: "lexical_rescue"
|
|
4740
|
+
};
|
|
4741
|
+
});
|
|
4742
|
+
return scored.filter((result) => result.score > 0).sort((a, b) => b.score - a.score).slice(0, args.top_k);
|
|
4743
|
+
}
|
|
4744
|
+
async function runSearchCodeSearch(args) {
|
|
4745
|
+
const topK = args.top_k ?? 10;
|
|
4746
|
+
const requestedThreshold = args.threshold ?? 0.2;
|
|
4747
|
+
const semanticTopK = Math.min(args.documents.length || topK, Math.max(topK * 3, topK + 5));
|
|
4748
|
+
const semanticDocuments = args.documents.map((doc) => ({ id: doc.id, content: doc.content }));
|
|
4749
|
+
const defaultResponse = await args.semantic_search({
|
|
4750
|
+
query: args.query,
|
|
4751
|
+
documents: semanticDocuments,
|
|
4752
|
+
top_k: semanticTopK,
|
|
4753
|
+
threshold: requestedThreshold
|
|
4754
|
+
});
|
|
4755
|
+
if (defaultResponse.results?.length) {
|
|
4756
|
+
return {
|
|
4757
|
+
results: defaultResponse.results.slice(0, topK).map((result) => ({ ...result, search_mode: "semantic" })),
|
|
4758
|
+
mode: "semantic",
|
|
4759
|
+
threshold_used: requestedThreshold,
|
|
4760
|
+
fallback_used: false,
|
|
4761
|
+
fallback_reason: null
|
|
4762
|
+
};
|
|
4763
|
+
}
|
|
4764
|
+
const adaptiveThreshold = Math.max(0.05, Math.min(requestedThreshold, requestedThreshold / 2));
|
|
4765
|
+
if (adaptiveThreshold < requestedThreshold) {
|
|
4766
|
+
const adaptiveResponse = await args.semantic_search({
|
|
4767
|
+
query: args.query,
|
|
4768
|
+
documents: semanticDocuments,
|
|
4769
|
+
top_k: semanticTopK,
|
|
4770
|
+
threshold: adaptiveThreshold
|
|
4771
|
+
});
|
|
4772
|
+
if (adaptiveResponse.results?.length) {
|
|
4773
|
+
return {
|
|
4774
|
+
results: adaptiveResponse.results.slice(0, topK).map((result) => ({ ...result, search_mode: "adaptive_semantic" })),
|
|
4775
|
+
mode: "adaptive_semantic",
|
|
4776
|
+
threshold_used: adaptiveThreshold,
|
|
4777
|
+
fallback_used: true,
|
|
4778
|
+
fallback_reason: `No results at threshold ${requestedThreshold}; lowered to ${adaptiveThreshold}.`
|
|
4779
|
+
};
|
|
4780
|
+
}
|
|
4781
|
+
}
|
|
4782
|
+
const lexicalResults = buildLexicalRescueResults({
|
|
4783
|
+
query: args.query,
|
|
4784
|
+
documents: args.documents,
|
|
4785
|
+
top_k: topK
|
|
4786
|
+
});
|
|
4787
|
+
return {
|
|
4788
|
+
results: lexicalResults,
|
|
4789
|
+
mode: "lexical_rescue",
|
|
4790
|
+
threshold_used: null,
|
|
4791
|
+
fallback_used: true,
|
|
4792
|
+
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."
|
|
4793
|
+
};
|
|
4794
|
+
}
|
|
4795
|
+
function buildLocalWorkspaceDocuments(rootPath, allowedExts, maxFiles) {
|
|
4796
|
+
const files = collectCodeFiles(rootPath, allowedExts, maxFiles);
|
|
4797
|
+
const documents = [];
|
|
4798
|
+
for (const filePath of files) {
|
|
4799
|
+
try {
|
|
4800
|
+
const stat = statSync(filePath);
|
|
4801
|
+
if (stat.size > 500 * 1024) continue;
|
|
4802
|
+
const content = readFileSync(filePath, "utf-8");
|
|
4803
|
+
const relPath = relative(rootPath, filePath).replace(/\\/g, "/");
|
|
4804
|
+
documents.push({ id: relPath, content: extractSignature(relPath, content), raw_content: content });
|
|
4805
|
+
} catch {
|
|
4806
|
+
}
|
|
4807
|
+
}
|
|
4808
|
+
return documents;
|
|
4809
|
+
}
|
|
4810
|
+
function extractRelevantSnippet(rawContent, query) {
|
|
4811
|
+
const lines = rawContent.split("\n");
|
|
4812
|
+
const tokens = tokenizeQueryForLexicalRescue(query);
|
|
4813
|
+
let matchIndex = -1;
|
|
4814
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
4815
|
+
const lower = lines[index].toLowerCase();
|
|
4816
|
+
if (tokens.some((token) => lower.includes(token))) {
|
|
4817
|
+
matchIndex = index;
|
|
4818
|
+
break;
|
|
4819
|
+
}
|
|
4820
|
+
}
|
|
4821
|
+
if (matchIndex < 0) {
|
|
4822
|
+
matchIndex = lines.findIndex((line) => line.trim().length > 0);
|
|
4823
|
+
}
|
|
4824
|
+
const start = Math.max(0, matchIndex < 0 ? 0 : matchIndex - 1);
|
|
4825
|
+
const end = Math.min(lines.length, start + 4);
|
|
4826
|
+
const snippet = lines.slice(start, end).join("\n").trim().slice(0, 500);
|
|
4827
|
+
return {
|
|
4828
|
+
snippet,
|
|
4829
|
+
line_start: start + 1,
|
|
4830
|
+
...end > start + 1 ? { line_end: end } : {}
|
|
4831
|
+
};
|
|
4832
|
+
}
|
|
4833
|
+
async function runLocalWorkspaceRetrieval(args) {
|
|
4834
|
+
const identity = resolveWorkspaceIdentity({ path: args.path });
|
|
4835
|
+
const workspace = await resolveWorkspaceTrust({ path: identity.root_path, project: args.project });
|
|
4836
|
+
const allowedExts = args.file_types ? new Set(args.file_types) : CODE_EXTENSIONS;
|
|
4837
|
+
const candidateFiles = collectCodeFiles(identity.root_path, allowedExts, args.max_files ?? 150);
|
|
4838
|
+
const documents = buildLocalWorkspaceDocuments(identity.root_path, allowedExts, args.max_files ?? 150);
|
|
4839
|
+
const sharedWarnings = [...workspace.warnings];
|
|
4840
|
+
const searchResult = await runSearchCodeSearch({
|
|
4841
|
+
query: args.query,
|
|
4842
|
+
documents,
|
|
4843
|
+
top_k: args.top_k,
|
|
4844
|
+
threshold: args.threshold,
|
|
4845
|
+
semantic_search: (params) => whisper.semanticSearch(params)
|
|
4846
|
+
});
|
|
4847
|
+
if (searchResult.mode === "lexical_rescue") {
|
|
4848
|
+
sharedWarnings.push("Local lexical rescue was used because semantic search returned no matches.");
|
|
4849
|
+
} else if (searchResult.mode === "adaptive_semantic") {
|
|
4850
|
+
sharedWarnings.push(searchResult.fallback_reason || "Adaptive semantic thresholding was used.");
|
|
4851
|
+
}
|
|
4852
|
+
if (!workspace.grounded_to_workspace && workspace.health !== "unbound" && workspace.health !== "unindexed") {
|
|
4853
|
+
sharedWarnings.push("Local code search is live, but project-backed retrieval may disagree until the workspace is re-indexed.");
|
|
4854
|
+
}
|
|
4855
|
+
const documentMap = new Map(documents.map((document) => [document.id, document]));
|
|
4856
|
+
const localResults = searchResult.results.map((result) => {
|
|
4857
|
+
const document = documentMap.get(result.id);
|
|
4858
|
+
const snippet = extractRelevantSnippet(document?.raw_content || result.snippet || "", args.query);
|
|
4859
|
+
return {
|
|
4860
|
+
...result,
|
|
4861
|
+
snippet: snippet.snippet || result.snippet,
|
|
4862
|
+
raw_snippet: snippet.snippet || result.snippet,
|
|
4863
|
+
line_start: snippet.line_start,
|
|
4864
|
+
...snippet.line_end ? { line_end: snippet.line_end } : {},
|
|
4865
|
+
retrieval_method: result.search_mode === "lexical_rescue" ? "lexical" : "semantic"
|
|
4866
|
+
};
|
|
4867
|
+
});
|
|
4868
|
+
return {
|
|
4869
|
+
workspace,
|
|
4870
|
+
documents,
|
|
4871
|
+
results: localResults,
|
|
4872
|
+
warnings: sharedWarnings,
|
|
4873
|
+
diagnostics: {
|
|
4874
|
+
workspace_id: workspace.workspace_id,
|
|
4875
|
+
root_path: workspace.root_path,
|
|
4876
|
+
project_ref: workspace.project_ref,
|
|
4877
|
+
project_id: workspace.project_id,
|
|
4878
|
+
identity_source: workspace.identity_source,
|
|
4879
|
+
index_health: workspace.health,
|
|
4880
|
+
grounded_to_workspace: true,
|
|
4881
|
+
threshold_requested: args.threshold ?? 0.2,
|
|
4882
|
+
threshold_used: searchResult.threshold_used,
|
|
4883
|
+
fallback_used: searchResult.fallback_used,
|
|
4884
|
+
fallback_reason: searchResult.fallback_reason,
|
|
4885
|
+
search_mode: searchResult.mode,
|
|
4886
|
+
candidate_files_scanned: candidateFiles.length,
|
|
4887
|
+
semantic_candidate_count: documents.length,
|
|
4888
|
+
retrieval_route: "local_workspace",
|
|
4889
|
+
warnings: sharedWarnings
|
|
4890
|
+
}
|
|
4891
|
+
};
|
|
4892
|
+
}
|
|
4893
|
+
function isLikelyRepoBackedResult(result) {
|
|
4894
|
+
const metadata = result.metadata || {};
|
|
4895
|
+
const retrievalSource = String(result.retrieval_source || "").toLowerCase();
|
|
4896
|
+
if (retrievalSource.includes("memory")) return false;
|
|
4897
|
+
const pathCandidate = String(metadata.file_path || metadata.path || result.source || result.document || "");
|
|
4898
|
+
if (/[\\/]/.test(pathCandidate) || /\.[a-z0-9]+$/i.test(pathCandidate)) return true;
|
|
4899
|
+
const sourceType = String(metadata.source_type || metadata.connector_type || result.type || "").toLowerCase();
|
|
4900
|
+
return ["local", "github", "code", "file", "repo"].some((token) => sourceType.includes(token));
|
|
4901
|
+
}
|
|
4902
|
+
function filterProjectRepoResults(results, matchedSourceIds) {
|
|
4903
|
+
const scoped = results.filter((result) => {
|
|
4904
|
+
const sourceId = String(result.metadata?.source_id || result.metadata?.sourceId || result.metadata?.source || "");
|
|
4905
|
+
return matchedSourceIds.length === 0 || matchedSourceIds.includes(sourceId);
|
|
4906
|
+
});
|
|
4907
|
+
const repoResults = scoped.filter((result) => isLikelyRepoBackedResult(result));
|
|
4908
|
+
return repoResults.length > 0 ? repoResults : scoped;
|
|
4909
|
+
}
|
|
4910
|
+
function localHitsToEvidence(workspaceId, hits) {
|
|
4911
|
+
return hits.map(
|
|
4912
|
+
(hit) => toEvidenceRef(
|
|
4913
|
+
{
|
|
4914
|
+
id: hit.id,
|
|
4915
|
+
content: hit.raw_snippet,
|
|
4916
|
+
score: hit.score,
|
|
4917
|
+
retrieval_source: hit.retrieval_method,
|
|
4918
|
+
metadata: {
|
|
4919
|
+
file_path: hit.id,
|
|
4920
|
+
snippet: hit.raw_snippet,
|
|
4921
|
+
line_start: hit.line_start,
|
|
4922
|
+
...hit.line_end ? { line_end: hit.line_end } : {}
|
|
4923
|
+
}
|
|
4924
|
+
},
|
|
4925
|
+
workspaceId,
|
|
4926
|
+
hit.retrieval_method
|
|
4927
|
+
)
|
|
4928
|
+
);
|
|
4929
|
+
}
|
|
4930
|
+
function renderLocalWorkspaceContext(args) {
|
|
4931
|
+
const lines = args.hits.map((hit, index) => {
|
|
4932
|
+
const location = hit.line_end && hit.line_end !== hit.line_start ? `${hit.id}:${hit.line_start}-${hit.line_end}` : `${hit.id}:${hit.line_start}`;
|
|
4933
|
+
return `${index + 1}. [${location}, score: ${hit.score.toFixed(2)}, mode: ${hit.search_mode}] ${hit.raw_snippet || hit.snippet || hit.id}`;
|
|
4934
|
+
});
|
|
4935
|
+
const header = `Found ${args.hits.length} local workspace result(s) (route=${args.route}, workspace=${args.diagnostics.workspace_id}, project=${args.project_ref || "none"}, user=${args.scope.userId}, session=${args.scope.sessionId}):`;
|
|
4936
|
+
const warnings = args.warnings.length ? `
|
|
4937
|
+
|
|
4938
|
+
[warnings]
|
|
4939
|
+
${args.warnings.join("\n")}` : "";
|
|
4940
|
+
return `${header}
|
|
4941
|
+
|
|
4942
|
+
${lines.join("\n\n")}${warnings}`;
|
|
4943
|
+
}
|
|
4944
|
+
async function runSearchCodeTool(args) {
|
|
4945
|
+
const rootPath = canonicalizeWorkspacePath(args.path);
|
|
4946
|
+
const allowedExts = args.file_types ? new Set(args.file_types) : CODE_EXTENSIONS;
|
|
4947
|
+
const files = collectCodeFiles(rootPath, allowedExts, args.max_files ?? 150);
|
|
4948
|
+
if (files.length === 0) {
|
|
4949
|
+
const workspace = await resolveWorkspaceTrust({ path: rootPath });
|
|
4950
|
+
const sharedWarnings = [...workspace.warnings];
|
|
4951
|
+
return {
|
|
4952
|
+
tool: "search_code",
|
|
4953
|
+
query: args.query,
|
|
4954
|
+
path: rootPath,
|
|
4955
|
+
results: [],
|
|
4956
|
+
count: 0,
|
|
4957
|
+
warnings: sharedWarnings,
|
|
4958
|
+
diagnostics: {
|
|
4959
|
+
workspace_id: workspace.workspace_id,
|
|
4960
|
+
root_path: workspace.root_path,
|
|
4961
|
+
project_ref: workspace.project_ref,
|
|
4962
|
+
project_id: workspace.project_id,
|
|
4963
|
+
identity_source: workspace.identity_source,
|
|
4964
|
+
index_health: workspace.health,
|
|
4965
|
+
grounded_to_workspace: workspace.grounded_to_workspace,
|
|
4966
|
+
threshold_requested: args.threshold ?? 0.2,
|
|
4967
|
+
threshold_used: null,
|
|
4968
|
+
fallback_used: false,
|
|
4969
|
+
fallback_reason: null,
|
|
4970
|
+
search_mode: "semantic",
|
|
4971
|
+
candidate_files_scanned: 0,
|
|
4972
|
+
semantic_candidate_count: 0,
|
|
4973
|
+
retrieval_route: "local_workspace",
|
|
4974
|
+
warnings: sharedWarnings
|
|
4975
|
+
}
|
|
4976
|
+
};
|
|
4977
|
+
}
|
|
4978
|
+
const localRetrieval = await runLocalWorkspaceRetrieval(args);
|
|
4979
|
+
return {
|
|
4980
|
+
tool: "search_code",
|
|
4981
|
+
query: args.query,
|
|
4982
|
+
path: rootPath,
|
|
4983
|
+
results: localRetrieval.results,
|
|
4984
|
+
count: localRetrieval.results.length,
|
|
4985
|
+
warnings: localRetrieval.warnings,
|
|
4986
|
+
diagnostics: localRetrieval.diagnostics
|
|
4987
|
+
};
|
|
4988
|
+
}
|
|
4221
4989
|
function getLocalAllowlistRoots() {
|
|
4222
4990
|
const fromEnv = (process.env.WHISPER_LOCAL_ALLOWLIST || "").split(",").map((v) => v.trim()).filter(Boolean);
|
|
4223
4991
|
if (fromEnv.length > 0) return fromEnv;
|
|
@@ -4297,7 +5065,8 @@ async function ingestLocalPath(params) {
|
|
|
4297
5065
|
}
|
|
4298
5066
|
collect(rootPath);
|
|
4299
5067
|
const manifest = loadIngestManifest();
|
|
4300
|
-
const
|
|
5068
|
+
const canonicalRootPath = canonicalizeWorkspacePath(rootPath);
|
|
5069
|
+
const workspaceId = getWorkspaceIdForPath(canonicalRootPath);
|
|
4301
5070
|
if (!manifest[workspaceId]) manifest[workspaceId] = { last_run_at: (/* @__PURE__ */ new Date(0)).toISOString(), files: {} };
|
|
4302
5071
|
const docs = [];
|
|
4303
5072
|
const skipped = [];
|
|
@@ -4333,6 +5102,10 @@ async function ingestLocalPath(params) {
|
|
|
4333
5102
|
}
|
|
4334
5103
|
manifest[workspaceId].last_run_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
4335
5104
|
saveIngestManifest(manifest);
|
|
5105
|
+
const state = loadState();
|
|
5106
|
+
const workspace = getWorkspaceState(state, workspaceId);
|
|
5107
|
+
workspace.root_path = canonicalRootPath;
|
|
5108
|
+
saveState(state);
|
|
4336
5109
|
appendFileSync(
|
|
4337
5110
|
AUDIT_LOG_PATH,
|
|
4338
5111
|
`${(/* @__PURE__ */ new Date()).toISOString()} local_ingest workspace=${workspaceId} root_hash=${createHash("sha256").update(rootPath).digest("hex").slice(0, 16)} files=${docs.length}
|
|
@@ -4488,22 +5261,27 @@ server.tool(
|
|
|
4488
5261
|
},
|
|
4489
5262
|
async ({ path, workspace_id, project }) => {
|
|
4490
5263
|
try {
|
|
4491
|
-
const
|
|
5264
|
+
const identity = resolveWorkspaceIdentity({ path, workspace_id });
|
|
4492
5265
|
const state = loadState();
|
|
4493
|
-
const existed = Boolean(state.workspaces[
|
|
4494
|
-
const
|
|
4495
|
-
const
|
|
4496
|
-
const resolvedBy = project?.trim() ? "explicit_project" : DEFAULT_PROJECT ? "env_default" : resolvedProject ? "auto_first_project" : "unresolved";
|
|
4497
|
-
saveState(state);
|
|
5266
|
+
const existed = Boolean(state.workspaces[identity.workspace_id]);
|
|
5267
|
+
const trust = await resolveWorkspaceTrust({ path: identity.root_path, workspace_id, project });
|
|
5268
|
+
const preflight = await resolveRepoGroundingPreflight({ query: "workspace status", path, workspace_id, project });
|
|
4498
5269
|
const payload = {
|
|
4499
|
-
workspace_id:
|
|
4500
|
-
|
|
5270
|
+
workspace_id: identity.workspace_id,
|
|
5271
|
+
root_path: trust.root_path,
|
|
5272
|
+
identity_source: trust.identity_source,
|
|
5273
|
+
project_ref: trust.project_ref,
|
|
5274
|
+
project_id: trust.project_id,
|
|
4501
5275
|
created: !existed,
|
|
4502
|
-
resolved_by:
|
|
5276
|
+
resolved_by: trust.resolved_by,
|
|
5277
|
+
health: trust.health,
|
|
5278
|
+
retrieval_readiness: preflight.retrieval_readiness,
|
|
5279
|
+
warnings: preflight.warnings,
|
|
5280
|
+
recommended_next_calls: preflight.recommended_next_calls,
|
|
4503
5281
|
index_state: {
|
|
4504
|
-
last_indexed_at:
|
|
4505
|
-
last_indexed_commit:
|
|
4506
|
-
coverage:
|
|
5282
|
+
last_indexed_at: trust.freshness.last_indexed_at,
|
|
5283
|
+
last_indexed_commit: trust.last_indexed_commit,
|
|
5284
|
+
coverage: trust.coverage
|
|
4507
5285
|
}
|
|
4508
5286
|
};
|
|
4509
5287
|
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
@@ -4521,24 +5299,30 @@ server.tool(
|
|
|
4521
5299
|
},
|
|
4522
5300
|
async ({ workspace_id, path }) => {
|
|
4523
5301
|
try {
|
|
4524
|
-
const
|
|
4525
|
-
const
|
|
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;
|
|
5302
|
+
const trust = await resolveWorkspaceTrust({ path, workspace_id });
|
|
5303
|
+
const repoVerification = await inspectProjectRepoSources({ project_ref: trust.project_ref, root_path: trust.root_path });
|
|
4531
5304
|
const payload = {
|
|
4532
|
-
workspace_id:
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
5305
|
+
workspace_id: trust.workspace_id,
|
|
5306
|
+
root_path: trust.root_path,
|
|
5307
|
+
identity_source: trust.identity_source,
|
|
5308
|
+
project_ref: trust.project_ref,
|
|
5309
|
+
project_id: trust.project_id,
|
|
5310
|
+
resolved_by: trust.resolved_by,
|
|
5311
|
+
health: trust.health,
|
|
5312
|
+
retrieval_readiness: repoVerification.retrieval_readiness,
|
|
5313
|
+
warnings: Array.from(/* @__PURE__ */ new Set([...trust.warnings, ...repoVerification.warnings])),
|
|
5314
|
+
recommended_next_calls: Array.from(
|
|
5315
|
+
/* @__PURE__ */ new Set([
|
|
5316
|
+
...trust.recommended_next_calls,
|
|
5317
|
+
...repoVerification.retrieval_readiness === "project_bound_no_repo_source" ? ["context.list_sources", "index.local_scan_ingest"] : []
|
|
5318
|
+
])
|
|
5319
|
+
),
|
|
5320
|
+
freshness: trust.freshness,
|
|
5321
|
+
coverage: trust.coverage,
|
|
5322
|
+
last_indexed_commit: trust.last_indexed_commit,
|
|
5323
|
+
current_commit: trust.current_commit,
|
|
5324
|
+
pending_changes: trust.pending_changes,
|
|
5325
|
+
grounded_to_workspace: trust.grounded_to_workspace
|
|
4542
5326
|
};
|
|
4543
5327
|
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
4544
5328
|
} catch (error) {
|
|
@@ -4557,13 +5341,15 @@ server.tool(
|
|
|
4557
5341
|
},
|
|
4558
5342
|
async ({ workspace_id, path, mode, max_files }) => {
|
|
4559
5343
|
try {
|
|
4560
|
-
const
|
|
4561
|
-
const
|
|
5344
|
+
const identity = resolveWorkspaceIdentity({ path, workspace_id });
|
|
5345
|
+
const rootPath = identity.root_path;
|
|
5346
|
+
const workspaceId = identity.workspace_id;
|
|
4562
5347
|
const state = loadState();
|
|
4563
5348
|
const workspace = getWorkspaceState(state, workspaceId);
|
|
4564
5349
|
const fileStats = countCodeFiles(rootPath, max_files);
|
|
4565
5350
|
const coverage = Math.max(0, Math.min(1, fileStats.total / Math.max(1, max_files)));
|
|
4566
5351
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5352
|
+
workspace.root_path = rootPath;
|
|
4567
5353
|
workspace.index_metadata = {
|
|
4568
5354
|
last_indexed_at: now,
|
|
4569
5355
|
last_indexed_commit: getGitHead(rootPath),
|
|
@@ -4572,6 +5358,7 @@ server.tool(
|
|
|
4572
5358
|
saveState(state);
|
|
4573
5359
|
const payload = {
|
|
4574
5360
|
workspace_id: workspaceId,
|
|
5361
|
+
root_path: rootPath,
|
|
4575
5362
|
mode,
|
|
4576
5363
|
indexed_files: fileStats.total,
|
|
4577
5364
|
skipped_files: fileStats.skipped,
|
|
@@ -4623,9 +5410,10 @@ server.tool(
|
|
|
4623
5410
|
);
|
|
4624
5411
|
server.tool(
|
|
4625
5412
|
"context.get_relevant",
|
|
4626
|
-
"
|
|
5413
|
+
"Default grounded retrieval step for workspace/project questions. Call this before answering when you need ranked evidence with file:line citations instead of relying on model memory.",
|
|
4627
5414
|
{
|
|
4628
5415
|
question: z.string().describe("Task/question to retrieve context for"),
|
|
5416
|
+
path: z.string().optional().describe("Workspace path. Defaults to current working directory."),
|
|
4629
5417
|
workspace_id: z.string().optional(),
|
|
4630
5418
|
project: z.string().optional(),
|
|
4631
5419
|
top_k: z.number().optional().default(12),
|
|
@@ -4634,45 +5422,115 @@ server.tool(
|
|
|
4634
5422
|
session_id: z.string().optional(),
|
|
4635
5423
|
user_id: z.string().optional()
|
|
4636
5424
|
},
|
|
4637
|
-
async ({ question, workspace_id, project, top_k, include_memories, include_graph, session_id, user_id }) => {
|
|
5425
|
+
async ({ question, path, workspace_id, project, top_k, include_memories, include_graph, session_id, user_id }) => {
|
|
4638
5426
|
try {
|
|
4639
|
-
const
|
|
4640
|
-
|
|
4641
|
-
|
|
5427
|
+
const preflight = await resolveRepoGroundingPreflight({ query: question, path, workspace_id, project });
|
|
5428
|
+
if (preflight.retrieval_route === "local_workspace_fallback") {
|
|
5429
|
+
const localRetrieval = await runLocalWorkspaceRetrieval({
|
|
5430
|
+
query: question,
|
|
5431
|
+
path: preflight.trust_state.root_path,
|
|
5432
|
+
project: preflight.trust_state.project_ref || project,
|
|
5433
|
+
top_k
|
|
5434
|
+
});
|
|
5435
|
+
const evidence2 = localHitsToEvidence(preflight.trust_state.workspace_id, localRetrieval.results);
|
|
5436
|
+
const payload2 = {
|
|
5437
|
+
question,
|
|
5438
|
+
workspace_id: preflight.trust_state.workspace_id,
|
|
5439
|
+
root_path: preflight.trust_state.root_path,
|
|
5440
|
+
identity_source: preflight.trust_state.identity_source,
|
|
5441
|
+
trust_state: preflight.trust_state,
|
|
5442
|
+
retrieval_readiness: preflight.retrieval_readiness,
|
|
5443
|
+
retrieval_route: preflight.retrieval_route,
|
|
5444
|
+
grounded_to_workspace: true,
|
|
5445
|
+
total_results: evidence2.length,
|
|
5446
|
+
context: evidence2.map((item) => `[${renderCitation(item)}] ${item.snippet || "Relevant local workspace context found."}`).join("\n"),
|
|
5447
|
+
evidence: evidence2,
|
|
5448
|
+
used_context_ids: evidence2.map((item) => item.source_id),
|
|
5449
|
+
latency_ms: 0,
|
|
5450
|
+
warnings: Array.from(/* @__PURE__ */ new Set([...preflight.warnings, ...localRetrieval.warnings])),
|
|
5451
|
+
recommended_next_calls: preflight.recommended_next_calls
|
|
5452
|
+
};
|
|
5453
|
+
return { content: [{ type: "text", text: JSON.stringify(payload2, null, 2) }] };
|
|
5454
|
+
}
|
|
5455
|
+
if (!preflight.trust_state.project_ref) {
|
|
4642
5456
|
const payload2 = {
|
|
4643
5457
|
question,
|
|
4644
|
-
workspace_id:
|
|
5458
|
+
workspace_id: preflight.trust_state.workspace_id,
|
|
5459
|
+
root_path: preflight.trust_state.root_path,
|
|
5460
|
+
identity_source: preflight.trust_state.identity_source,
|
|
5461
|
+
trust_state: preflight.trust_state,
|
|
5462
|
+
retrieval_readiness: preflight.retrieval_readiness,
|
|
5463
|
+
retrieval_route: "none",
|
|
5464
|
+
grounded_to_workspace: false,
|
|
4645
5465
|
total_results: 0,
|
|
4646
5466
|
context: "",
|
|
4647
5467
|
evidence: [],
|
|
4648
5468
|
used_context_ids: [],
|
|
4649
5469
|
latency_ms: 0,
|
|
4650
|
-
|
|
5470
|
+
warnings: preflight.warnings,
|
|
5471
|
+
recommended_next_calls: preflight.recommended_next_calls
|
|
4651
5472
|
};
|
|
4652
5473
|
return { content: [{ type: "text", text: JSON.stringify(payload2, null, 2) }] };
|
|
4653
5474
|
}
|
|
4654
5475
|
const queryResult = await queryWithDegradedFallback({
|
|
4655
|
-
project:
|
|
5476
|
+
project: preflight.trust_state.project_ref,
|
|
4656
5477
|
query: question,
|
|
4657
5478
|
top_k,
|
|
4658
|
-
include_memories,
|
|
5479
|
+
include_memories: preflight.repo_grounded ? false : include_memories,
|
|
4659
5480
|
include_graph,
|
|
4660
5481
|
session_id,
|
|
4661
|
-
user_id
|
|
5482
|
+
user_id,
|
|
5483
|
+
source_ids: preflight.repo_grounded ? preflight.matched_source_ids : void 0
|
|
4662
5484
|
});
|
|
4663
5485
|
const response = queryResult.response;
|
|
4664
|
-
const
|
|
5486
|
+
const rawResults = preflight.repo_grounded ? filterProjectRepoResults(response.results || [], preflight.matched_source_ids) : response.results || [];
|
|
5487
|
+
if (preflight.repo_grounded && rawResults.length === 0) {
|
|
5488
|
+
const localRetrieval = await runLocalWorkspaceRetrieval({
|
|
5489
|
+
query: question,
|
|
5490
|
+
path: preflight.trust_state.root_path,
|
|
5491
|
+
project: preflight.trust_state.project_ref,
|
|
5492
|
+
top_k
|
|
5493
|
+
});
|
|
5494
|
+
const evidence2 = localHitsToEvidence(preflight.trust_state.workspace_id, localRetrieval.results);
|
|
5495
|
+
const payload2 = {
|
|
5496
|
+
question,
|
|
5497
|
+
workspace_id: preflight.trust_state.workspace_id,
|
|
5498
|
+
root_path: preflight.trust_state.root_path,
|
|
5499
|
+
identity_source: preflight.trust_state.identity_source,
|
|
5500
|
+
trust_state: preflight.trust_state,
|
|
5501
|
+
retrieval_readiness: "local_fallback",
|
|
5502
|
+
retrieval_route: "local_workspace_fallback",
|
|
5503
|
+
grounded_to_workspace: true,
|
|
5504
|
+
total_results: evidence2.length,
|
|
5505
|
+
context: evidence2.map((item) => `[${renderCitation(item)}] ${item.snippet || "Relevant local workspace context found."}`).join("\n"),
|
|
5506
|
+
evidence: evidence2,
|
|
5507
|
+
used_context_ids: evidence2.map((item) => item.source_id),
|
|
5508
|
+
latency_ms: 0,
|
|
5509
|
+
warnings: Array.from(/* @__PURE__ */ new Set([...preflight.warnings, ...localRetrieval.warnings])),
|
|
5510
|
+
recommended_next_calls: preflight.recommended_next_calls
|
|
5511
|
+
};
|
|
5512
|
+
return { content: [{ type: "text", text: JSON.stringify(payload2, null, 2) }] };
|
|
5513
|
+
}
|
|
5514
|
+
const evidence = rawResults.map((r) => toEvidenceRef(r, preflight.trust_state.workspace_id, "semantic"));
|
|
4665
5515
|
const payload = {
|
|
4666
5516
|
question,
|
|
4667
|
-
workspace_id:
|
|
4668
|
-
|
|
5517
|
+
workspace_id: preflight.trust_state.workspace_id,
|
|
5518
|
+
root_path: preflight.trust_state.root_path,
|
|
5519
|
+
identity_source: preflight.trust_state.identity_source,
|
|
5520
|
+
trust_state: preflight.trust_state,
|
|
5521
|
+
retrieval_readiness: preflight.retrieval_readiness,
|
|
5522
|
+
retrieval_route: preflight.repo_grounded ? "project_repo" : "none",
|
|
5523
|
+
grounded_to_workspace: preflight.repo_grounded ? true : preflight.trust_state.grounded_to_workspace,
|
|
5524
|
+
total_results: rawResults.length || response.meta?.total || evidence.length,
|
|
4669
5525
|
context: response.context || "",
|
|
4670
5526
|
evidence,
|
|
4671
|
-
used_context_ids:
|
|
5527
|
+
used_context_ids: rawResults.map((r) => String(r.id)),
|
|
4672
5528
|
latency_ms: response.meta?.latency_ms || 0,
|
|
4673
5529
|
degraded_mode: queryResult.degraded_mode,
|
|
4674
5530
|
degraded_reason: queryResult.degraded_reason,
|
|
4675
|
-
recommendation: queryResult.recommendation
|
|
5531
|
+
recommendation: queryResult.recommendation,
|
|
5532
|
+
warnings: preflight.warnings,
|
|
5533
|
+
recommended_next_calls: preflight.recommended_next_calls
|
|
4676
5534
|
};
|
|
4677
5535
|
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
4678
5536
|
} catch (error) {
|
|
@@ -4685,36 +5543,53 @@ server.tool(
|
|
|
4685
5543
|
"Verify whether a claim is supported by retrieved context. Returns supported/partial/unsupported with evidence.",
|
|
4686
5544
|
{
|
|
4687
5545
|
claim: z.string().describe("Claim to verify"),
|
|
5546
|
+
path: z.string().optional().describe("Workspace path. Defaults to current working directory."),
|
|
4688
5547
|
workspace_id: z.string().optional(),
|
|
4689
5548
|
project: z.string().optional(),
|
|
4690
5549
|
context_ids: z.array(z.string()).optional(),
|
|
4691
5550
|
strict: z.boolean().optional().default(true)
|
|
4692
5551
|
},
|
|
4693
|
-
async ({ claim, workspace_id, project, context_ids, strict }) => {
|
|
5552
|
+
async ({ claim, path, workspace_id, project, context_ids, strict }) => {
|
|
4694
5553
|
try {
|
|
4695
|
-
const
|
|
4696
|
-
|
|
4697
|
-
if (
|
|
5554
|
+
const preflight = await resolveRepoGroundingPreflight({ query: claim, path, workspace_id, project });
|
|
5555
|
+
let evidence = [];
|
|
5556
|
+
if (preflight.retrieval_route === "local_workspace_fallback") {
|
|
5557
|
+
const localRetrieval = await runLocalWorkspaceRetrieval({
|
|
5558
|
+
query: claim,
|
|
5559
|
+
path: preflight.trust_state.root_path,
|
|
5560
|
+
project: preflight.trust_state.project_ref || project,
|
|
5561
|
+
top_k: strict ? 8 : 12
|
|
5562
|
+
});
|
|
5563
|
+
evidence = localHitsToEvidence(preflight.trust_state.workspace_id, localRetrieval.results).filter((item) => !context_ids || context_ids.length === 0 || context_ids.includes(item.source_id));
|
|
5564
|
+
} else if (preflight.trust_state.project_ref) {
|
|
5565
|
+
const response = await whisper.query({
|
|
5566
|
+
project: preflight.trust_state.project_ref,
|
|
5567
|
+
query: claim,
|
|
5568
|
+
top_k: strict ? 8 : 12,
|
|
5569
|
+
include_memories: preflight.repo_grounded ? false : true,
|
|
5570
|
+
include_graph: true,
|
|
5571
|
+
...preflight.repo_grounded ? { source_ids: preflight.matched_source_ids } : {}
|
|
5572
|
+
});
|
|
5573
|
+
const filtered = (preflight.repo_grounded ? filterProjectRepoResults(response.results || [], preflight.matched_source_ids) : response.results || []).filter(
|
|
5574
|
+
(r) => !context_ids || context_ids.length === 0 || context_ids.includes(String(r.id))
|
|
5575
|
+
);
|
|
5576
|
+
evidence = filtered.map((r) => toEvidenceRef(r, preflight.trust_state.workspace_id, "semantic"));
|
|
5577
|
+
}
|
|
5578
|
+
if (evidence.length === 0) {
|
|
4698
5579
|
const payload2 = {
|
|
4699
5580
|
verdict: "unsupported",
|
|
4700
5581
|
confidence: 0,
|
|
4701
5582
|
evidence: [],
|
|
4702
|
-
|
|
4703
|
-
|
|
5583
|
+
trust_state: preflight.trust_state,
|
|
5584
|
+
retrieval_readiness: preflight.retrieval_readiness,
|
|
5585
|
+
retrieval_route: preflight.retrieval_route,
|
|
5586
|
+
warnings: preflight.warnings,
|
|
5587
|
+
recommended_next_calls: preflight.recommended_next_calls,
|
|
5588
|
+
missing_requirements: preflight.warnings.length ? preflight.warnings : ["No repo-grounded evidence was available for verification."],
|
|
5589
|
+
explanation: "Verifier did not find sufficient repo-grounded evidence."
|
|
4704
5590
|
};
|
|
4705
5591
|
return { content: [{ type: "text", text: JSON.stringify(payload2, null, 2) }] };
|
|
4706
5592
|
}
|
|
4707
|
-
const response = await whisper.query({
|
|
4708
|
-
project: resolvedProject,
|
|
4709
|
-
query: claim,
|
|
4710
|
-
top_k: strict ? 8 : 12,
|
|
4711
|
-
include_memories: true,
|
|
4712
|
-
include_graph: true
|
|
4713
|
-
});
|
|
4714
|
-
const filtered = (response.results || []).filter(
|
|
4715
|
-
(r) => !context_ids || context_ids.length === 0 || context_ids.includes(String(r.id))
|
|
4716
|
-
);
|
|
4717
|
-
const evidence = filtered.map((r) => toEvidenceRef(r, workspaceId, "semantic"));
|
|
4718
5593
|
const directEvidence = evidence.filter((e) => e.score >= (strict ? 0.7 : 0.6));
|
|
4719
5594
|
const weakEvidence = evidence.filter((e) => e.score >= (strict ? 0.45 : 0.35));
|
|
4720
5595
|
let verdict = "unsupported";
|
|
@@ -4724,6 +5599,11 @@ server.tool(
|
|
|
4724
5599
|
verdict,
|
|
4725
5600
|
confidence: evidence.length ? Math.max(...evidence.map((e) => e.score)) : 0,
|
|
4726
5601
|
evidence: verdict === "supported" ? directEvidence : weakEvidence,
|
|
5602
|
+
trust_state: preflight.trust_state,
|
|
5603
|
+
retrieval_readiness: preflight.retrieval_readiness,
|
|
5604
|
+
retrieval_route: preflight.retrieval_route,
|
|
5605
|
+
warnings: preflight.warnings,
|
|
5606
|
+
recommended_next_calls: preflight.recommended_next_calls,
|
|
4727
5607
|
missing_requirements: verdict === "supported" ? [] : verdict === "partial" ? ["No direct evidence spans met strict threshold."] : ["No sufficient supporting evidence found for the claim."],
|
|
4728
5608
|
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
5609
|
};
|
|
@@ -4738,6 +5618,7 @@ server.tool(
|
|
|
4738
5618
|
"Answer a question only when evidence requirements are met. Fails closed with an abstain payload when not verifiable.",
|
|
4739
5619
|
{
|
|
4740
5620
|
question: z.string(),
|
|
5621
|
+
path: z.string().optional().describe("Workspace path. Defaults to current working directory."),
|
|
4741
5622
|
workspace_id: z.string().optional(),
|
|
4742
5623
|
project: z.string().optional(),
|
|
4743
5624
|
constraints: z.object({
|
|
@@ -4752,41 +5633,53 @@ server.tool(
|
|
|
4752
5633
|
include_recent_decisions: z.boolean().optional().default(true)
|
|
4753
5634
|
}).optional()
|
|
4754
5635
|
},
|
|
4755
|
-
async ({ question, workspace_id, project, constraints, retrieval }) => {
|
|
5636
|
+
async ({ question, path, workspace_id, project, constraints, retrieval }) => {
|
|
4756
5637
|
try {
|
|
4757
|
-
const workspaceId = getWorkspaceId(workspace_id);
|
|
4758
5638
|
const requireCitations = constraints?.require_citations ?? true;
|
|
4759
5639
|
const minEvidenceItems = constraints?.min_evidence_items ?? 2;
|
|
4760
5640
|
const minConfidence = constraints?.min_confidence ?? 0.65;
|
|
4761
5641
|
const maxStalenessHours = constraints?.max_staleness_hours ?? 168;
|
|
4762
5642
|
const topK = retrieval?.top_k ?? 12;
|
|
4763
|
-
const
|
|
4764
|
-
|
|
5643
|
+
const preflight = await resolveRepoGroundingPreflight({ query: question, path, workspace_id, project });
|
|
5644
|
+
let evidence = [];
|
|
5645
|
+
if (preflight.retrieval_route === "local_workspace_fallback") {
|
|
5646
|
+
const localRetrieval = await runLocalWorkspaceRetrieval({
|
|
5647
|
+
query: question,
|
|
5648
|
+
path: preflight.trust_state.root_path,
|
|
5649
|
+
project: preflight.trust_state.project_ref || project,
|
|
5650
|
+
top_k: topK
|
|
5651
|
+
});
|
|
5652
|
+
evidence = localHitsToEvidence(preflight.trust_state.workspace_id, localRetrieval.results);
|
|
5653
|
+
} else if (preflight.trust_state.project_ref) {
|
|
5654
|
+
const response = await whisper.query({
|
|
5655
|
+
project: preflight.trust_state.project_ref,
|
|
5656
|
+
query: question,
|
|
5657
|
+
top_k: topK,
|
|
5658
|
+
include_memories: preflight.repo_grounded ? false : true,
|
|
5659
|
+
include_graph: true,
|
|
5660
|
+
...preflight.repo_grounded ? { source_ids: preflight.matched_source_ids } : {}
|
|
5661
|
+
});
|
|
5662
|
+
const filtered = preflight.repo_grounded ? filterProjectRepoResults(response.results || [], preflight.matched_source_ids) : response.results || [];
|
|
5663
|
+
evidence = filtered.map((r) => toEvidenceRef(r, preflight.trust_state.workspace_id, "semantic"));
|
|
5664
|
+
}
|
|
5665
|
+
if (evidence.length === 0) {
|
|
5666
|
+
const reason = preflight.trust_state.health === "stale" || preflight.trust_state.health === "drifted" ? "stale_index" : "no_retrieval_hits";
|
|
4765
5667
|
const abstain = buildAbstain({
|
|
4766
|
-
reason
|
|
4767
|
-
message:
|
|
5668
|
+
reason,
|
|
5669
|
+
message: preflight.warnings[0] || "Workspace trust requirements were not met.",
|
|
4768
5670
|
closest_evidence: [],
|
|
4769
5671
|
claims_evaluated: 1,
|
|
4770
5672
|
evidence_items_found: 0,
|
|
4771
5673
|
min_required: minEvidenceItems,
|
|
4772
|
-
index_fresh:
|
|
5674
|
+
index_fresh: preflight.trust_state.health === "healthy",
|
|
5675
|
+
warnings: preflight.warnings,
|
|
5676
|
+
trust_state: preflight.trust_state,
|
|
5677
|
+
recommended_next_calls: preflight.recommended_next_calls
|
|
4773
5678
|
});
|
|
4774
5679
|
return { content: [{ type: "text", text: JSON.stringify(abstain, null, 2) }] };
|
|
4775
5680
|
}
|
|
4776
|
-
const response = await whisper.query({
|
|
4777
|
-
project: resolvedProject,
|
|
4778
|
-
query: question,
|
|
4779
|
-
top_k: topK,
|
|
4780
|
-
include_memories: true,
|
|
4781
|
-
include_graph: true
|
|
4782
|
-
});
|
|
4783
|
-
const evidence = (response.results || []).map((r) => toEvidenceRef(r, workspaceId, "semantic"));
|
|
4784
5681
|
const sorted = evidence.sort((a, b) => b.score - a.score);
|
|
4785
5682
|
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
5683
|
if (sorted.length === 0) {
|
|
4791
5684
|
const abstain = buildAbstain({
|
|
4792
5685
|
reason: "no_retrieval_hits",
|
|
@@ -4795,24 +5688,15 @@ server.tool(
|
|
|
4795
5688
|
claims_evaluated: 1,
|
|
4796
5689
|
evidence_items_found: 0,
|
|
4797
5690
|
min_required: minEvidenceItems,
|
|
4798
|
-
index_fresh:
|
|
5691
|
+
index_fresh: preflight.trust_state.health === "healthy",
|
|
5692
|
+
warnings: preflight.warnings,
|
|
5693
|
+
trust_state: preflight.trust_state,
|
|
5694
|
+
recommended_next_calls: preflight.recommended_next_calls
|
|
4799
5695
|
});
|
|
4800
5696
|
return { content: [{ type: "text", text: JSON.stringify(abstain, null, 2) }] };
|
|
4801
5697
|
}
|
|
4802
5698
|
const supportedEvidence = sorted.filter((e) => e.score >= minConfidence);
|
|
4803
5699
|
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
5700
|
if (requireCitations && (verdict !== "supported" || supportedEvidence.length < minEvidenceItems)) {
|
|
4817
5701
|
const abstain = buildAbstain({
|
|
4818
5702
|
reason: "insufficient_evidence",
|
|
@@ -4821,7 +5705,10 @@ server.tool(
|
|
|
4821
5705
|
claims_evaluated: 1,
|
|
4822
5706
|
evidence_items_found: supportedEvidence.length,
|
|
4823
5707
|
min_required: minEvidenceItems,
|
|
4824
|
-
index_fresh: true
|
|
5708
|
+
index_fresh: true,
|
|
5709
|
+
warnings: preflight.warnings,
|
|
5710
|
+
trust_state: preflight.trust_state,
|
|
5711
|
+
recommended_next_calls: preflight.recommended_next_calls
|
|
4825
5712
|
});
|
|
4826
5713
|
return { content: [{ type: "text", text: JSON.stringify(abstain, null, 2) }] };
|
|
4827
5714
|
}
|
|
@@ -4834,6 +5721,11 @@ server.tool(
|
|
|
4834
5721
|
answer: answerLines.join("\n"),
|
|
4835
5722
|
citations,
|
|
4836
5723
|
confidence,
|
|
5724
|
+
trust_state: preflight.trust_state,
|
|
5725
|
+
retrieval_readiness: preflight.retrieval_readiness,
|
|
5726
|
+
retrieval_route: preflight.retrieval_route,
|
|
5727
|
+
warnings: preflight.warnings,
|
|
5728
|
+
recommended_next_calls: preflight.recommended_next_calls,
|
|
4837
5729
|
verification: {
|
|
4838
5730
|
verdict,
|
|
4839
5731
|
supported_claims: verdict === "supported" ? 1 : 0,
|
|
@@ -4849,10 +5741,11 @@ server.tool(
|
|
|
4849
5741
|
);
|
|
4850
5742
|
server.tool(
|
|
4851
5743
|
"context.query",
|
|
4852
|
-
"
|
|
5744
|
+
"Use this when answering from project knowledge rather than general model memory. Retrieves packed context for a query using hybrid vector+keyword search with optional memory/graph expansion.",
|
|
4853
5745
|
{
|
|
4854
5746
|
project: z.string().optional().describe("Project name or slug (optional if WHISPER_PROJECT is set)"),
|
|
4855
5747
|
query: z.string().describe("What are you looking for?"),
|
|
5748
|
+
path: z.string().optional().describe("Workspace path. Defaults to current working directory."),
|
|
4856
5749
|
top_k: z.number().optional().default(10).describe("Number of results"),
|
|
4857
5750
|
chunk_types: z.array(z.string()).optional().describe("Filter: code, function, class, documentation, api_spec, schema, config, text"),
|
|
4858
5751
|
include_memories: z.boolean().optional().describe("Include relevant memories. Omit to use automatic runtime defaults."),
|
|
@@ -4861,13 +5754,105 @@ server.tool(
|
|
|
4861
5754
|
session_id: z.string().optional().describe("Session ID for memory scoping"),
|
|
4862
5755
|
max_tokens: z.number().optional().describe("Max tokens for packed context")
|
|
4863
5756
|
},
|
|
4864
|
-
async ({ project, query, top_k, chunk_types, include_memories, include_graph, user_id, session_id, max_tokens }) => {
|
|
5757
|
+
async ({ project, query, path, top_k, chunk_types, include_memories, include_graph, user_id, session_id, max_tokens }) => {
|
|
4865
5758
|
try {
|
|
4866
|
-
const
|
|
4867
|
-
|
|
5759
|
+
const preflight = await resolveRepoGroundingPreflight({ query, path, project, chunk_types });
|
|
5760
|
+
const resolvedProject = preflight.trust_state.project_ref || await resolveProjectRef(project);
|
|
5761
|
+
if (!resolvedProject && !preflight.repo_grounded) {
|
|
4868
5762
|
return { content: [{ type: "text", text: "Error: No project resolved. Set WHISPER_PROJECT or pass project." }] };
|
|
4869
5763
|
}
|
|
4870
|
-
const
|
|
5764
|
+
const scope = resolveMcpScope({ project: resolvedProject, user_id, session_id, path: preflight.trust_state.root_path });
|
|
5765
|
+
if (preflight.repo_grounded && preflight.retrieval_route === "local_workspace_fallback") {
|
|
5766
|
+
const localRetrieval = await runLocalWorkspaceRetrieval({
|
|
5767
|
+
query,
|
|
5768
|
+
path: preflight.trust_state.root_path,
|
|
5769
|
+
project: resolvedProject || project,
|
|
5770
|
+
top_k
|
|
5771
|
+
});
|
|
5772
|
+
if (localRetrieval.results.length > 0) {
|
|
5773
|
+
return {
|
|
5774
|
+
content: [{
|
|
5775
|
+
type: "text",
|
|
5776
|
+
text: renderLocalWorkspaceContext({
|
|
5777
|
+
query,
|
|
5778
|
+
project_ref: resolvedProject || null,
|
|
5779
|
+
scope,
|
|
5780
|
+
route: "local_workspace_fallback",
|
|
5781
|
+
hits: localRetrieval.results,
|
|
5782
|
+
diagnostics: localRetrieval.diagnostics,
|
|
5783
|
+
warnings: Array.from(/* @__PURE__ */ new Set([...preflight.warnings, ...localRetrieval.warnings]))
|
|
5784
|
+
})
|
|
5785
|
+
}]
|
|
5786
|
+
};
|
|
5787
|
+
}
|
|
5788
|
+
}
|
|
5789
|
+
if (preflight.repo_grounded && resolvedProject) {
|
|
5790
|
+
const queryResult2 = await queryWithDegradedFallback({
|
|
5791
|
+
project: resolvedProject,
|
|
5792
|
+
query,
|
|
5793
|
+
top_k,
|
|
5794
|
+
include_memories: false,
|
|
5795
|
+
include_graph,
|
|
5796
|
+
user_id: user_id || scope.userId,
|
|
5797
|
+
session_id: session_id || scope.sessionId,
|
|
5798
|
+
source_ids: preflight.matched_source_ids
|
|
5799
|
+
});
|
|
5800
|
+
const repoResults = filterProjectRepoResults(queryResult2.response.results || [], preflight.matched_source_ids);
|
|
5801
|
+
if (repoResults.length > 0) {
|
|
5802
|
+
const scopedResponse = { ...queryResult2.response, results: repoResults };
|
|
5803
|
+
const header2 = `Found ${repoResults.length} repo-grounded result(s) (${scopedResponse.meta.latency_ms}ms${scopedResponse.meta.cache_hit ? ", cached" : ""}, route=project_repo, workspace=${preflight.trust_state.workspace_id}, project=${resolvedProject}, user=${scope.userId}, session=${scope.sessionId}):
|
|
5804
|
+
|
|
5805
|
+
`;
|
|
5806
|
+
const suffix2 = [
|
|
5807
|
+
`[diagnostics] identity_source=${preflight.trust_state.identity_source} retrieval_readiness=${preflight.retrieval_readiness} retrieval_route=project_repo`,
|
|
5808
|
+
queryResult2.degraded_mode ? `[degraded_mode=true] ${queryResult2.degraded_reason}
|
|
5809
|
+
Recommendation: ${queryResult2.recommendation}` : "",
|
|
5810
|
+
preflight.warnings.length ? `[warnings]
|
|
5811
|
+
${preflight.warnings.join("\n")}` : ""
|
|
5812
|
+
].filter(Boolean).join("\n\n");
|
|
5813
|
+
return { content: [{ type: "text", text: `${header2}${scopedResponse.context}${suffix2 ? `
|
|
5814
|
+
|
|
5815
|
+
${suffix2}` : ""}` }] };
|
|
5816
|
+
}
|
|
5817
|
+
}
|
|
5818
|
+
if (preflight.repo_grounded) {
|
|
5819
|
+
if (resolvedProject && include_memories !== false) {
|
|
5820
|
+
const memoryRescue = await runContextQueryMemoryRescue({
|
|
5821
|
+
project: resolvedProject,
|
|
5822
|
+
query,
|
|
5823
|
+
user_id: user_id ? scope.userId : void 0,
|
|
5824
|
+
session_id: session_id ? scope.sessionId : void 0,
|
|
5825
|
+
top_k
|
|
5826
|
+
});
|
|
5827
|
+
if (memoryRescue.results.length && memoryRescue.rescue_mode) {
|
|
5828
|
+
return {
|
|
5829
|
+
content: [{
|
|
5830
|
+
type: "text",
|
|
5831
|
+
text: `${renderContextQueryMemoryRescue({
|
|
5832
|
+
project: resolvedProject,
|
|
5833
|
+
query,
|
|
5834
|
+
scope,
|
|
5835
|
+
results: memoryRescue.results,
|
|
5836
|
+
rescue_mode: memoryRescue.rescue_mode
|
|
5837
|
+
})}
|
|
5838
|
+
|
|
5839
|
+
[diagnostics]
|
|
5840
|
+
retrieval_route=memory_only retrieval_readiness=${preflight.retrieval_readiness} workspace=${preflight.trust_state.workspace_id}`
|
|
5841
|
+
}]
|
|
5842
|
+
};
|
|
5843
|
+
}
|
|
5844
|
+
}
|
|
5845
|
+
return {
|
|
5846
|
+
content: [{
|
|
5847
|
+
type: "text",
|
|
5848
|
+
text: `No relevant repo-grounded context found.
|
|
5849
|
+
|
|
5850
|
+
[diagnostics]
|
|
5851
|
+
workspace=${preflight.trust_state.workspace_id} identity_source=${preflight.trust_state.identity_source} retrieval_readiness=${preflight.retrieval_readiness} retrieval_route=${preflight.retrieval_route}`
|
|
5852
|
+
}]
|
|
5853
|
+
};
|
|
5854
|
+
}
|
|
5855
|
+
const automaticMode = !preflight.repo_grounded && include_memories !== false && include_graph !== true && !(chunk_types && chunk_types.length > 0) && max_tokens === void 0 && runtimeClient;
|
|
4871
5856
|
if (automaticMode) {
|
|
4872
5857
|
try {
|
|
4873
5858
|
const prepared = await prepareAutomaticQuery({
|
|
@@ -4875,9 +5860,31 @@ server.tool(
|
|
|
4875
5860
|
query,
|
|
4876
5861
|
top_k,
|
|
4877
5862
|
user_id,
|
|
4878
|
-
session_id
|
|
5863
|
+
session_id,
|
|
5864
|
+
path: preflight.trust_state.root_path
|
|
4879
5865
|
});
|
|
4880
5866
|
if (!prepared.items.length) {
|
|
5867
|
+
const memoryRescue = include_memories !== false ? await runContextQueryMemoryRescue({
|
|
5868
|
+
project: resolvedProject,
|
|
5869
|
+
query,
|
|
5870
|
+
user_id: user_id ? scope2.userId : void 0,
|
|
5871
|
+
session_id: session_id ? scope2.sessionId : void 0,
|
|
5872
|
+
top_k
|
|
5873
|
+
}) : { results: [], rescue_mode: null };
|
|
5874
|
+
if (memoryRescue.results.length && memoryRescue.rescue_mode) {
|
|
5875
|
+
return {
|
|
5876
|
+
content: [{
|
|
5877
|
+
type: "text",
|
|
5878
|
+
text: renderContextQueryMemoryRescue({
|
|
5879
|
+
project: resolvedProject,
|
|
5880
|
+
query,
|
|
5881
|
+
scope: scope2,
|
|
5882
|
+
results: memoryRescue.results,
|
|
5883
|
+
rescue_mode: memoryRescue.rescue_mode
|
|
5884
|
+
})
|
|
5885
|
+
}]
|
|
5886
|
+
};
|
|
5887
|
+
}
|
|
4881
5888
|
return { content: [{ type: "text", text: "No relevant context found." }] };
|
|
4882
5889
|
}
|
|
4883
5890
|
const warnings = prepared.retrieval.warnings.length ? `
|
|
@@ -4907,18 +5914,41 @@ ${prepared.context}${warnings}`
|
|
|
4907
5914
|
top_k,
|
|
4908
5915
|
include_memories: include_memories === true,
|
|
4909
5916
|
include_graph,
|
|
4910
|
-
user_id: user_id ||
|
|
4911
|
-
session_id: session_id ||
|
|
5917
|
+
user_id: user_id || scope.userId,
|
|
5918
|
+
session_id: session_id || scope.sessionId
|
|
4912
5919
|
});
|
|
4913
5920
|
const response2 = queryResult2.response;
|
|
4914
5921
|
if (response2.results.length === 0) {
|
|
5922
|
+
const memoryRescue = include_memories !== false ? await runContextQueryMemoryRescue({
|
|
5923
|
+
project: resolvedProject,
|
|
5924
|
+
query,
|
|
5925
|
+
user_id: user_id ? scope.userId : void 0,
|
|
5926
|
+
session_id: session_id ? scope.sessionId : void 0,
|
|
5927
|
+
top_k
|
|
5928
|
+
}) : { results: [], rescue_mode: null };
|
|
5929
|
+
if (memoryRescue.results.length && memoryRescue.rescue_mode) {
|
|
5930
|
+
return {
|
|
5931
|
+
content: [{
|
|
5932
|
+
type: "text",
|
|
5933
|
+
text: `${renderContextQueryMemoryRescue({
|
|
5934
|
+
project: resolvedProject,
|
|
5935
|
+
query,
|
|
5936
|
+
scope,
|
|
5937
|
+
results: memoryRescue.results,
|
|
5938
|
+
rescue_mode: memoryRescue.rescue_mode
|
|
5939
|
+
})}
|
|
5940
|
+
|
|
5941
|
+
[automatic_runtime]
|
|
5942
|
+
${automaticWarning}`
|
|
5943
|
+
}]
|
|
5944
|
+
};
|
|
5945
|
+
}
|
|
4915
5946
|
return { content: [{ type: "text", text: `No relevant context found.
|
|
4916
5947
|
|
|
4917
5948
|
[automatic_runtime]
|
|
4918
5949
|
${automaticWarning}` }] };
|
|
4919
5950
|
}
|
|
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}):
|
|
5951
|
+
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
5952
|
|
|
4923
5953
|
`;
|
|
4924
5954
|
const suffix2 = queryResult2.degraded_mode ? `
|
|
@@ -4937,14 +5967,34 @@ ${automaticWarning}${suffix2}` }] };
|
|
|
4937
5967
|
top_k,
|
|
4938
5968
|
include_memories: include_memories === true,
|
|
4939
5969
|
include_graph,
|
|
4940
|
-
user_id: user_id ||
|
|
4941
|
-
session_id: session_id ||
|
|
5970
|
+
user_id: user_id || scope.userId,
|
|
5971
|
+
session_id: session_id || scope.sessionId
|
|
4942
5972
|
});
|
|
4943
5973
|
const response = queryResult.response;
|
|
4944
5974
|
if (response.results.length === 0) {
|
|
5975
|
+
const memoryRescue = include_memories !== false ? await runContextQueryMemoryRescue({
|
|
5976
|
+
project: resolvedProject,
|
|
5977
|
+
query,
|
|
5978
|
+
user_id: user_id ? scope.userId : void 0,
|
|
5979
|
+
session_id: session_id ? scope.sessionId : void 0,
|
|
5980
|
+
top_k
|
|
5981
|
+
}) : { results: [], rescue_mode: null };
|
|
5982
|
+
if (memoryRescue.results.length && memoryRescue.rescue_mode) {
|
|
5983
|
+
return {
|
|
5984
|
+
content: [{
|
|
5985
|
+
type: "text",
|
|
5986
|
+
text: renderContextQueryMemoryRescue({
|
|
5987
|
+
project: resolvedProject,
|
|
5988
|
+
query,
|
|
5989
|
+
scope,
|
|
5990
|
+
results: memoryRescue.results,
|
|
5991
|
+
rescue_mode: memoryRescue.rescue_mode
|
|
5992
|
+
})
|
|
5993
|
+
}]
|
|
5994
|
+
};
|
|
5995
|
+
}
|
|
4945
5996
|
return { content: [{ type: "text", text: "No relevant context found." }] };
|
|
4946
5997
|
}
|
|
4947
|
-
const scope = resolveMcpScope({ user_id, session_id });
|
|
4948
5998
|
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
5999
|
|
|
4950
6000
|
`;
|
|
@@ -4996,7 +6046,7 @@ server.tool(
|
|
|
4996
6046
|
);
|
|
4997
6047
|
server.tool(
|
|
4998
6048
|
"memory.search",
|
|
4999
|
-
"
|
|
6049
|
+
"Call this before answering questions about user history (preferences, prior decisions, past tasks, or 'what did we discuss/search'). Returns memory context you would not otherwise know.",
|
|
5000
6050
|
{
|
|
5001
6051
|
project: z.string().optional().describe("Project name or slug"),
|
|
5002
6052
|
query: z.string().describe("What to search for"),
|
|
@@ -5587,6 +6637,7 @@ server.tool(
|
|
|
5587
6637
|
return { content: [{ type: "text", text: "Error: target.memory_id or target.query is required." }] };
|
|
5588
6638
|
}
|
|
5589
6639
|
const affectedIds = [];
|
|
6640
|
+
let queryResolution = null;
|
|
5590
6641
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5591
6642
|
const actor = process.env.WHISPER_AGENT_ID || process.env.USERNAME || "api_key_principal";
|
|
5592
6643
|
const resolvedProject = await resolveProjectRef(project);
|
|
@@ -5645,24 +6696,18 @@ server.tool(
|
|
|
5645
6696
|
project: resolvedProject,
|
|
5646
6697
|
query: target.query || "",
|
|
5647
6698
|
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;
|
|
6699
|
+
include_relations: false,
|
|
6700
|
+
include_pending: true
|
|
5659
6701
|
});
|
|
5660
|
-
const
|
|
6702
|
+
const resolved = resolveForgetQueryCandidates(search, target.query || "");
|
|
6703
|
+
queryResolution = resolved.resolved_by;
|
|
6704
|
+
const memoryIds = resolved.memory_ids;
|
|
5661
6705
|
if (memoryIds.length === 0) {
|
|
5662
6706
|
const payload2 = {
|
|
5663
6707
|
status: "completed",
|
|
5664
6708
|
affected_ids: affectedIds,
|
|
5665
|
-
warning: "Query did not resolve to
|
|
6709
|
+
warning: resolved.warning || "Query did not resolve to a reliable memory match. No memories were changed.",
|
|
6710
|
+
resolved_by: resolved.resolved_by
|
|
5666
6711
|
};
|
|
5667
6712
|
return { content: [{ type: "text", text: JSON.stringify(payload2, null, 2) }] };
|
|
5668
6713
|
}
|
|
@@ -5684,6 +6729,7 @@ server.tool(
|
|
|
5684
6729
|
const payload = {
|
|
5685
6730
|
status: "completed",
|
|
5686
6731
|
affected_ids: affectedIds,
|
|
6732
|
+
...queryResolution ? { resolved_by: queryResolution } : {},
|
|
5687
6733
|
audit: {
|
|
5688
6734
|
audit_id: audit.audit_id,
|
|
5689
6735
|
actor: audit.actor,
|
|
@@ -5978,30 +7024,56 @@ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next"
|
|
|
5978
7024
|
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
7025
|
function extractSignature(filePath, content) {
|
|
5980
7026
|
const lines = content.split("\n");
|
|
5981
|
-
const signature = [`// File: ${filePath}`];
|
|
7027
|
+
const signature = /* @__PURE__ */ new Set([`// File: ${filePath}`]);
|
|
7028
|
+
signature.add(`relative_path:${filePath.replace(/\\/g, "/").toLowerCase()}`);
|
|
7029
|
+
for (const segment of filePath.split(/[\\/._-]+/).filter(Boolean)) {
|
|
7030
|
+
signature.add(`path:${segment}`);
|
|
7031
|
+
}
|
|
5982
7032
|
const head = lines.slice(0, 60);
|
|
5983
7033
|
for (const line of head) {
|
|
5984
7034
|
const trimmed = line.trim();
|
|
5985
7035
|
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*")) continue;
|
|
7036
|
+
const urlMatches = trimmed.match(/https?:\/\/[^\s"'`]+/g);
|
|
7037
|
+
if (urlMatches) {
|
|
7038
|
+
for (const match of urlMatches.slice(0, 2)) signature.add(`url:${match.slice(0, 120)}`);
|
|
7039
|
+
}
|
|
7040
|
+
const routeMatches = trimmed.match(/\/[A-Za-z0-9._~!$&'()*+,;=:@%/-]{2,}/g);
|
|
7041
|
+
if (routeMatches) {
|
|
7042
|
+
for (const match of routeMatches.slice(0, 3)) signature.add(`route:${match.slice(0, 120)}`);
|
|
7043
|
+
}
|
|
7044
|
+
const envKeyMatches = trimmed.match(/[A-Z][A-Z0-9_]{2,}/g);
|
|
7045
|
+
if (envKeyMatches) {
|
|
7046
|
+
for (const match of envKeyMatches.slice(0, 3)) signature.add(`env:${match}`);
|
|
7047
|
+
}
|
|
5986
7048
|
if (/^(import|from|require|use |pub use )/.test(trimmed)) {
|
|
5987
|
-
signature.
|
|
7049
|
+
signature.add(trimmed.slice(0, 120));
|
|
5988
7050
|
continue;
|
|
5989
7051
|
}
|
|
5990
7052
|
if (/^(export|async function|function|class|interface|type |const |let |def |pub fn |fn |struct |impl |enum )/.test(trimmed)) {
|
|
5991
|
-
signature.
|
|
7053
|
+
signature.add(trimmed.slice(0, 120));
|
|
7054
|
+
const exportMatch = trimmed.match(/export\s+(?:const|function|class|type|interface)\s+([A-Za-z0-9_$]+)/);
|
|
7055
|
+
if (exportMatch?.[1]) signature.add(`export:${exportMatch[1]}`);
|
|
5992
7056
|
continue;
|
|
5993
7057
|
}
|
|
5994
7058
|
if (trimmed.startsWith("@") || trimmed.startsWith("#[")) {
|
|
5995
|
-
signature.
|
|
7059
|
+
signature.add(trimmed.slice(0, 80));
|
|
5996
7060
|
}
|
|
5997
7061
|
}
|
|
5998
|
-
for (
|
|
5999
|
-
const trimmed =
|
|
7062
|
+
for (let index = 60; index < lines.length; index += 1) {
|
|
7063
|
+
const trimmed = lines[index].trim();
|
|
6000
7064
|
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.
|
|
7065
|
+
signature.add(trimmed.slice(0, 120));
|
|
7066
|
+
const surrounding = lines.slice(index, Math.min(lines.length, index + 3)).map((line) => line.trim()).filter(Boolean);
|
|
7067
|
+
for (const line of surrounding) signature.add(line.slice(0, 120));
|
|
7068
|
+
continue;
|
|
6002
7069
|
}
|
|
7070
|
+
if (/^[A-Za-z0-9_$]+\s*[:=]\s*["'`][^"'`]{3,}["'`]/.test(trimmed)) {
|
|
7071
|
+
signature.add(trimmed.slice(0, 120));
|
|
7072
|
+
}
|
|
7073
|
+
const exportMatch = trimmed.match(/export\s+(?:const|function|class|type|interface)\s+([A-Za-z0-9_$]+)/);
|
|
7074
|
+
if (exportMatch?.[1]) signature.add(`export:${exportMatch[1]}`);
|
|
6003
7075
|
}
|
|
6004
|
-
return signature.join("\n").slice(0,
|
|
7076
|
+
return Array.from(signature).join("\n").slice(0, 3200);
|
|
6005
7077
|
}
|
|
6006
7078
|
server.tool(
|
|
6007
7079
|
"code.search_semantic",
|
|
@@ -6015,91 +7087,21 @@ server.tool(
|
|
|
6015
7087
|
max_files: z.number().optional().default(150).describe("Max files to scan. For large codebases, narrow with file_types instead of raising this.")
|
|
6016
7088
|
},
|
|
6017
7089
|
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
7090
|
try {
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
7091
|
+
return primaryToolSuccess({
|
|
7092
|
+
...await runSearchCodeTool({
|
|
7093
|
+
query,
|
|
7094
|
+
path: searchPath,
|
|
7095
|
+
file_types,
|
|
7096
|
+
top_k,
|
|
7097
|
+
threshold,
|
|
7098
|
+
max_files
|
|
7099
|
+
}),
|
|
7100
|
+
tool: "code.search_semantic"
|
|
6067
7101
|
});
|
|
6068
7102
|
} 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("");
|
|
7103
|
+
return primaryToolError(`Semantic search failed: ${error.message}`);
|
|
6101
7104
|
}
|
|
6102
|
-
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
6103
7105
|
}
|
|
6104
7106
|
);
|
|
6105
7107
|
function* walkDir(dir, fileTypes) {
|
|
@@ -6338,7 +7340,7 @@ server.tool(
|
|
|
6338
7340
|
);
|
|
6339
7341
|
server.tool(
|
|
6340
7342
|
"search",
|
|
6341
|
-
"
|
|
7343
|
+
"Primary retrieval alias. Call this whenever the user asks to find or recall context: use `query` for semantic retrieval, `id` for exact memory fetch, or both for hybrid recall.",
|
|
6342
7344
|
{
|
|
6343
7345
|
project: z.string().optional().describe("Project name or slug"),
|
|
6344
7346
|
query: z.string().optional().describe("Semantic retrieval query"),
|
|
@@ -6416,72 +7418,15 @@ server.tool(
|
|
|
6416
7418
|
max_files: z.number().optional().default(150)
|
|
6417
7419
|
},
|
|
6418
7420
|
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
7421
|
try {
|
|
6463
|
-
|
|
6464
|
-
query,
|
|
6465
|
-
documents,
|
|
6466
|
-
top_k: top_k ?? 10,
|
|
6467
|
-
threshold: threshold ?? 0.2
|
|
6468
|
-
});
|
|
6469
|
-
if (!response.results?.length) {
|
|
6470
|
-
return primaryToolSuccess({
|
|
6471
|
-
tool: "search_code",
|
|
6472
|
-
query,
|
|
6473
|
-
path: rootPath,
|
|
6474
|
-
results: [],
|
|
6475
|
-
count: 0
|
|
6476
|
-
});
|
|
6477
|
-
}
|
|
6478
|
-
return primaryToolSuccess({
|
|
6479
|
-
tool: "search_code",
|
|
7422
|
+
return primaryToolSuccess(await runSearchCodeTool({
|
|
6480
7423
|
query,
|
|
6481
|
-
path
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
7424
|
+
path,
|
|
7425
|
+
file_types,
|
|
7426
|
+
top_k,
|
|
7427
|
+
threshold,
|
|
7428
|
+
max_files
|
|
7429
|
+
}));
|
|
6485
7430
|
} catch (error) {
|
|
6486
7431
|
return primaryToolError(`Semantic search failed: ${error.message}`);
|
|
6487
7432
|
}
|
|
@@ -6625,7 +7570,7 @@ server.tool(
|
|
|
6625
7570
|
);
|
|
6626
7571
|
server.tool(
|
|
6627
7572
|
"index",
|
|
6628
|
-
"
|
|
7573
|
+
"Administrative indexing tool. Call this when retrieval is stale/missing or the user asks to connect new data. Use action='source' to add GitHub/web/pdf/local/slack/video, action='workspace' to refresh local workspace metadata.",
|
|
6629
7574
|
{
|
|
6630
7575
|
action: z.enum(["source", "workspace"]).default("source"),
|
|
6631
7576
|
project: z.string().optional(),
|
|
@@ -6706,7 +7651,7 @@ server.tool(
|
|
|
6706
7651
|
);
|
|
6707
7652
|
server.tool(
|
|
6708
7653
|
"remember",
|
|
6709
|
-
"
|
|
7654
|
+
"Call this whenever the user states a durable preference, decision, instruction, or personal/project fact that should persist across sessions. Save proactively without waiting for an explicit 'remember this'.",
|
|
6710
7655
|
{
|
|
6711
7656
|
project: z.string().optional(),
|
|
6712
7657
|
content: z.string().describe("Memory content"),
|
|
@@ -6785,7 +7730,7 @@ server.tool(
|
|
|
6785
7730
|
);
|
|
6786
7731
|
server.tool(
|
|
6787
7732
|
"learn",
|
|
6788
|
-
"Unified
|
|
7733
|
+
"Unified ingestion entrypoint. Call this when the user asks to import knowledge: mode='conversation' for chat logs, mode='text' for raw text, mode='source' for external sources to index. Prefer this over legacy compatibility tools.",
|
|
6789
7734
|
{
|
|
6790
7735
|
mode: z.enum(["conversation", "text", "source"]).describe("What kind of learning to perform"),
|
|
6791
7736
|
project: z.string().optional(),
|
|
@@ -6916,8 +7861,17 @@ if (process.argv[1] && /server\.(mjs|cjs|js|ts)$/.test(process.argv[1])) {
|
|
|
6916
7861
|
main().catch(console.error);
|
|
6917
7862
|
}
|
|
6918
7863
|
export {
|
|
7864
|
+
canonicalizeWorkspacePath,
|
|
7865
|
+
chooseWorkspaceProjectSource,
|
|
7866
|
+
classifyProjectRepoReadiness,
|
|
7867
|
+
classifyRepoGroundedQuery,
|
|
7868
|
+
classifyWorkspaceHealth,
|
|
6919
7869
|
createMcpServer,
|
|
6920
7870
|
createWhisperMcpClient,
|
|
6921
7871
|
createWhisperMcpRuntimeClient,
|
|
6922
|
-
|
|
7872
|
+
extractSignature,
|
|
7873
|
+
renderScopedMcpConfig,
|
|
7874
|
+
resolveForgetQueryCandidates,
|
|
7875
|
+
resolveWorkspaceIdentity,
|
|
7876
|
+
runSearchCodeSearch
|
|
6923
7877
|
};
|