@usewhisper/mcp-server 2.12.0 → 2.14.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.
Files changed (3) hide show
  1. package/README.md +5 -2
  2. package/dist/server.js +1134 -503
  3. package/package.json +2 -2
package/dist/server.js CHANGED
@@ -3785,6 +3785,9 @@ function buildMcpSearchPayload(input) {
3785
3785
  count: normalizedResults.length,
3786
3786
  degraded_mode: Boolean(input.degradedMode),
3787
3787
  degraded_reason: input.degradedReason ?? null,
3788
+ semantic_status: input.semanticStatus ?? (input.degradedMode ? "failed" : "ok"),
3789
+ fallback_mode: input.fallbackMode ?? (input.degradedMode ? "lexical_backend" : "none"),
3790
+ recommended_fixes: input.recommendedFixes || [],
3788
3791
  warnings: input.warnings || []
3789
3792
  });
3790
3793
  }
@@ -3844,6 +3847,8 @@ var RETRIEVAL_READINESS_VALUES = [
3844
3847
  "local_fallback"
3845
3848
  ];
3846
3849
  var RETRIEVAL_ROUTE_VALUES = ["project_repo", "local_workspace_fallback", "memory_only", "none"];
3850
+ var STATUS_LABEL_VALUES = ["healthy", "degraded_auto_repairing", "needs_user_action", "blocked"];
3851
+ var STATUS_ACTION_VALUES = ["none", "run_auto_repair", "restart_client", "reauth", "add_source", "reindex"];
3847
3852
  var STATE_DIR = join(homedir(), ".whisper-mcp");
3848
3853
  var STATE_PATH = join(STATE_DIR, "state.json");
3849
3854
  var AUDIT_LOG_PATH = join(STATE_DIR, "forget-audit.log");
@@ -3876,6 +3881,7 @@ var ALIAS_TOOL_MAP = [
3876
3881
  { alias: "search", target: "context.query | memory.get" },
3877
3882
  { alias: "search_code", target: "code.search_semantic" },
3878
3883
  { alias: "grep", target: "code.search_text" },
3884
+ { alias: "fix", target: "index.auto_repair" },
3879
3885
  { alias: "read", target: "local.file_read" },
3880
3886
  { alias: "explore", target: "local.tree" },
3881
3887
  { alias: "research", target: "research.oracle" },
@@ -3891,10 +3897,14 @@ var RECOMMENDED_NEXT_CALL_ALLOWLIST = /* @__PURE__ */ new Set([
3891
3897
  "context.list_projects",
3892
3898
  "index.workspace_status",
3893
3899
  "index.workspace_run",
3900
+ "index.auto_repair",
3901
+ "fix",
3894
3902
  "search_code",
3895
3903
  "grep",
3896
3904
  "context.list_sources",
3905
+ "context.add_source",
3897
3906
  "index.local_scan_ingest",
3907
+ "context.query",
3898
3908
  "context.get_relevant"
3899
3909
  ]);
3900
3910
  var UNTRUSTED_CODEBASE_HEALTH = /* @__PURE__ */ new Set(["unbound", "unindexed"]);
@@ -3902,12 +3912,85 @@ var UNTRUSTED_CODEBASE_PROJECT_READINESS = /* @__PURE__ */ new Set([
3902
3912
  "project_unverified",
3903
3913
  "project_bound_no_repo_source"
3904
3914
  ]);
3915
+ var RETRIEVAL_READINESS_RANK = {
3916
+ no_project: 0,
3917
+ project_unverified: 0,
3918
+ project_bound_no_repo_source: 1,
3919
+ project_repo_source_stale: 2,
3920
+ local_fallback: 3,
3921
+ project_repo_source_ready: 4
3922
+ };
3923
+ var ROOT_CAUSE_PRIMARY_ACTION = {
3924
+ none: "none",
3925
+ missing_api_key: "reauth",
3926
+ no_project_bound: "add_source",
3927
+ mode_mismatch_remote_local_ingest: "restart_client",
3928
+ workspace_unindexed: "reindex",
3929
+ project_bound_no_repo_source: "add_source",
3930
+ project_unverified: "reauth",
3931
+ project_repo_source_stale: "reindex",
3932
+ semantic_backend_unavailable: "run_auto_repair",
3933
+ local_fallback_active: "run_auto_repair"
3934
+ };
3935
+ var ROOT_CAUSE_NEXT_CALLS = {
3936
+ none: [],
3937
+ missing_api_key: [],
3938
+ no_project_bound: ["context.list_projects"],
3939
+ mode_mismatch_remote_local_ingest: [],
3940
+ workspace_unindexed: ["index.workspace_run"],
3941
+ project_bound_no_repo_source: ["context.add_source"],
3942
+ project_unverified: [],
3943
+ project_repo_source_stale: ["index.workspace_run"],
3944
+ semantic_backend_unavailable: ["fix"],
3945
+ local_fallback_active: ["fix"]
3946
+ };
3905
3947
  function sanitizeRecommendedNextCalls(nextCalls) {
3906
3948
  return Array.from(
3907
3949
  new Set(
3908
3950
  nextCalls.map((entry) => String(entry || "").trim()).filter((entry) => RECOMMENDED_NEXT_CALL_ALLOWLIST.has(entry))
3909
3951
  )
3910
- );
3952
+ ).slice(0, 3);
3953
+ }
3954
+ function recommendedNextCallsAreAcyclic(nextCalls) {
3955
+ const seen = /* @__PURE__ */ new Set();
3956
+ for (const nextCall of nextCalls) {
3957
+ if (seen.has(nextCall)) return false;
3958
+ seen.add(nextCall);
3959
+ }
3960
+ return true;
3961
+ }
3962
+ function retrievalReadinessRank(readiness) {
3963
+ return RETRIEVAL_READINESS_RANK[readiness];
3964
+ }
3965
+ function isStrictlyBetterReadiness(from, to) {
3966
+ return retrievalReadinessRank(to) > retrievalReadinessRank(from);
3967
+ }
3968
+ function statusActionForRootCause(rootCause) {
3969
+ return ROOT_CAUSE_PRIMARY_ACTION[rootCause] || "none";
3970
+ }
3971
+ function recommendedNextCallsForRootCause(rootCause) {
3972
+ return sanitizeRecommendedNextCalls(ROOT_CAUSE_NEXT_CALLS[rootCause] || []);
3973
+ }
3974
+ function statusLabelForAction(action) {
3975
+ if (action === "none") return "healthy";
3976
+ if (action === "run_auto_repair") return "degraded_auto_repairing";
3977
+ if (action === "reauth") return "blocked";
3978
+ return "needs_user_action";
3979
+ }
3980
+ function actionRequiredForAction(action) {
3981
+ return action !== "none" && action !== "run_auto_repair";
3982
+ }
3983
+ function getRepoReadinessNextCalls(readiness) {
3984
+ if (readiness === "project_bound_no_repo_source") {
3985
+ return recommendedNextCallsForRootCause("project_bound_no_repo_source");
3986
+ }
3987
+ if (readiness === "project_unverified") {
3988
+ return recommendedNextCallsForRootCause("project_unverified");
3989
+ }
3990
+ if (readiness === "project_repo_source_stale") {
3991
+ return recommendedNextCallsForRootCause("project_repo_source_stale");
3992
+ }
3993
+ return [];
3911
3994
  }
3912
3995
  function resolveMcpRetrievalProfile(requested) {
3913
3996
  if (requested && MCP_RETRIEVAL_PROFILE_VALUES.includes(requested)) {
@@ -3915,6 +3998,156 @@ function resolveMcpRetrievalProfile(requested) {
3915
3998
  }
3916
3999
  return MCP_RETRIEVAL_PRECISION_V1_DEFAULT ? "precision_v1" : "legacy";
3917
4000
  }
4001
+ function statusContractForReadiness(args) {
4002
+ if (args.root_cause_code && args.root_cause_code !== "none") {
4003
+ const action = statusActionForRootCause(args.root_cause_code);
4004
+ return {
4005
+ status_label: statusLabelForAction(action),
4006
+ status_action: action,
4007
+ action_required: actionRequiredForAction(action)
4008
+ };
4009
+ }
4010
+ if (args.force_repairing) {
4011
+ return {
4012
+ status_label: "degraded_auto_repairing",
4013
+ status_action: "run_auto_repair",
4014
+ action_required: false
4015
+ };
4016
+ }
4017
+ if (args.semantic_status === "failed") {
4018
+ return {
4019
+ status_label: "degraded_auto_repairing",
4020
+ status_action: "run_auto_repair",
4021
+ action_required: false
4022
+ };
4023
+ }
4024
+ if (args.workspace_health === "unindexed") {
4025
+ return {
4026
+ status_label: "needs_user_action",
4027
+ status_action: "reindex",
4028
+ action_required: true
4029
+ };
4030
+ }
4031
+ if (args.retrieval_readiness === "project_unverified") {
4032
+ return {
4033
+ status_label: "blocked",
4034
+ status_action: "reauth",
4035
+ action_required: true
4036
+ };
4037
+ }
4038
+ if (args.retrieval_readiness === "project_bound_no_repo_source") {
4039
+ if ((args.runtime_mode || RUNTIME_MODE) === "remote") {
4040
+ return {
4041
+ status_label: "needs_user_action",
4042
+ status_action: "add_source",
4043
+ action_required: true
4044
+ };
4045
+ }
4046
+ return {
4047
+ status_label: "needs_user_action",
4048
+ status_action: "reindex",
4049
+ action_required: true
4050
+ };
4051
+ }
4052
+ if (args.retrieval_readiness === "project_repo_source_stale") {
4053
+ return {
4054
+ status_label: "needs_user_action",
4055
+ status_action: "reindex",
4056
+ action_required: true
4057
+ };
4058
+ }
4059
+ if (args.retrieval_readiness === "local_fallback") {
4060
+ return {
4061
+ status_label: "degraded_auto_repairing",
4062
+ status_action: "run_auto_repair",
4063
+ action_required: false
4064
+ };
4065
+ }
4066
+ if (args.retrieval_readiness === "no_project") {
4067
+ return {
4068
+ status_label: "needs_user_action",
4069
+ status_action: "add_source",
4070
+ action_required: true
4071
+ };
4072
+ }
4073
+ return {
4074
+ status_label: "healthy",
4075
+ status_action: "none",
4076
+ action_required: false
4077
+ };
4078
+ }
4079
+ function semanticStatusFromSearchMode(mode) {
4080
+ if (mode === "semantic") return "ok";
4081
+ if (mode === "adaptive_semantic") return "adaptive";
4082
+ return "failed";
4083
+ }
4084
+ function fallbackModeFromSearchMode(mode) {
4085
+ if (mode === "semantic") return "none";
4086
+ if (mode === "adaptive_semantic") return "adaptive_threshold";
4087
+ return "lexical_rescue";
4088
+ }
4089
+ function parsePositiveNumber(value, fallback = 0) {
4090
+ const parsed = Number(value);
4091
+ if (!Number.isFinite(parsed) || parsed < 0) return fallback;
4092
+ return parsed;
4093
+ }
4094
+ function getWorkspaceSemanticFailureStats(workspaceId) {
4095
+ const state = loadState();
4096
+ const workspace = getWorkspaceState(state, workspaceId);
4097
+ const failures = parsePositiveNumber(workspace.index_metadata?.semantic_failures, 0);
4098
+ const attempts = parsePositiveNumber(workspace.index_metadata?.semantic_attempts, 0);
4099
+ const rate = attempts > 0 ? failures / attempts : 0;
4100
+ return { failures, attempts, rate };
4101
+ }
4102
+ function recordSemanticAttempt(workspaceId, failed) {
4103
+ const state = loadState();
4104
+ const workspace = getWorkspaceState(state, workspaceId);
4105
+ if (!workspace.index_metadata) workspace.index_metadata = {};
4106
+ const failures = parsePositiveNumber(workspace.index_metadata.semantic_failures, 0);
4107
+ const attempts = parsePositiveNumber(workspace.index_metadata.semantic_attempts, 0);
4108
+ workspace.index_metadata.semantic_attempts = attempts + 1;
4109
+ workspace.index_metadata.semantic_failures = failed ? failures + 1 : failures;
4110
+ saveState(state);
4111
+ }
4112
+ function computeTrustScore(args) {
4113
+ let score = 100;
4114
+ if (args.workspace_health === "unbound" || args.workspace_health === "unindexed") score -= 35;
4115
+ if (args.workspace_health === "stale" || args.workspace_health === "drifted") score -= 18;
4116
+ if (args.retrieval_readiness === "project_unverified") score -= 35;
4117
+ if (args.retrieval_readiness === "project_bound_no_repo_source") score -= 32;
4118
+ if (args.retrieval_readiness === "project_repo_source_stale") score -= 20;
4119
+ if (args.retrieval_readiness === "local_fallback") score -= 10;
4120
+ const failureRate = Math.max(0, Math.min(1, args.semantic_failure_rate || 0));
4121
+ score -= Math.round(failureRate * 25);
4122
+ return Math.max(0, Math.min(100, score));
4123
+ }
4124
+ function persistTrustScore(workspaceId, trustScore) {
4125
+ const state = loadState();
4126
+ const workspace = getWorkspaceState(state, workspaceId);
4127
+ if (!workspace.index_metadata) workspace.index_metadata = {};
4128
+ workspace.index_metadata.trust_score = Math.max(0, Math.min(100, Math.round(trustScore)));
4129
+ saveState(state);
4130
+ }
4131
+ function shouldAutoHeal(args) {
4132
+ if (args.retrieval_readiness === "project_unverified" || args.retrieval_readiness === "project_bound_no_repo_source") {
4133
+ return true;
4134
+ }
4135
+ if ((args.semantic_attempts || 0) >= 3 && args.semantic_failure_rate >= 0.5) {
4136
+ return true;
4137
+ }
4138
+ return args.trust_score < 60;
4139
+ }
4140
+ function resolveAutoRepairRootCause(args) {
4141
+ if (args.restart_required) return "mode_mismatch_remote_local_ingest";
4142
+ if (args.trust_health === "unindexed") return "workspace_unindexed";
4143
+ if (args.project_readiness === "project_bound_no_repo_source") return "project_bound_no_repo_source";
4144
+ if (args.project_readiness === "project_unverified") return "project_unverified";
4145
+ if (args.project_readiness === "project_repo_source_stale") return "project_repo_source_stale";
4146
+ if (args.semantic_attempts >= 3 && args.semantic_failure_rate >= 0.5) return "semantic_backend_unavailable";
4147
+ if (args.retrieval_readiness === "local_fallback") return "local_fallback_active";
4148
+ if (args.retrieval_readiness === "no_project") return "no_project_bound";
4149
+ return "none";
4150
+ }
3918
4151
  function shouldAbstainForCodebaseTrust(args) {
3919
4152
  if (args.retrievalProfile !== "precision_v1" || !args.codebaseIntent) return false;
3920
4153
  return UNTRUSTED_CODEBASE_HEALTH.has(args.preflight.trust_state.health) || UNTRUSTED_CODEBASE_PROJECT_READINESS.has(args.preflight.project_retrieval_readiness);
@@ -4250,9 +4483,9 @@ async function resolveProjectDescriptor(projectRef) {
4250
4483
  }
4251
4484
  }
4252
4485
  function getWorkspaceRecommendedNextCalls(health) {
4253
- if (health === "unbound") return sanitizeRecommendedNextCalls(["index.workspace_resolve", "context.list_projects", "index.workspace_status"]);
4254
- if (health === "unindexed") return sanitizeRecommendedNextCalls(["index.workspace_status", "index.workspace_run", "search_code", "grep"]);
4255
- if (health === "stale" || health === "drifted") return sanitizeRecommendedNextCalls(["index.workspace_status", "index.workspace_run", "grep", "search_code"]);
4486
+ if (health === "unbound") return sanitizeRecommendedNextCalls(["index.workspace_resolve"]);
4487
+ if (health === "unindexed") return sanitizeRecommendedNextCalls(["index.workspace_run"]);
4488
+ if (health === "stale" || health === "drifted") return sanitizeRecommendedNextCalls(["index.workspace_run"]);
4256
4489
  return [];
4257
4490
  }
4258
4491
  function getWorkspaceWarnings(args) {
@@ -4448,8 +4681,8 @@ async function resolveRepoGroundingPreflight(args) {
4448
4681
  }
4449
4682
  }
4450
4683
  const recommendedNextCalls = [...trust.recommended_next_calls];
4451
- if (sourceVerification.retrieval_readiness === "project_bound_no_repo_source" || sourceVerification.retrieval_readiness === "project_unverified") {
4452
- recommendedNextCalls.push("context.list_sources", "index.local_scan_ingest");
4684
+ if (sourceVerification.retrieval_readiness !== "project_repo_source_ready" && sourceVerification.retrieval_readiness !== "no_project") {
4685
+ recommendedNextCalls.push(...getRepoReadinessNextCalls(sourceVerification.retrieval_readiness));
4453
4686
  }
4454
4687
  return {
4455
4688
  repo_grounded: repoGrounded,
@@ -4478,53 +4711,32 @@ async function ingestSessionWithSyncFallback(params) {
4478
4711
  return whisper.ingestSession(params);
4479
4712
  }
4480
4713
  }
4481
- function defaultMcpUserId() {
4714
+ function defaultMcpUserId(params) {
4482
4715
  const explicit = process.env.WHISPER_USER_ID?.trim();
4483
4716
  if (explicit) return explicit;
4484
- const seed = `${process.cwd()}|${DEFAULT_PROJECT || "default"}|${API_KEY.slice(0, 12) || "anon"}`;
4717
+ const workspacePath = params?.workspacePath || canonicalizeWorkspacePath(process.cwd());
4718
+ const projectRef = params?.project || DEFAULT_PROJECT || "default";
4719
+ const seed = `${workspacePath}|${projectRef}|${API_KEY.slice(0, 12) || "anon"}`;
4485
4720
  return `mcp-user-${createHash("sha256").update(seed).digest("hex").slice(0, 12)}`;
4486
4721
  }
4487
- function resolveMcpScope(params) {
4722
+ function resolveMcpScope(params, options) {
4723
+ const workspacePath = canonicalizeWorkspacePath(params?.path || process.env.WHISPER_WORKSPACE_PATH || process.cwd());
4724
+ const explicitSession = params?.session_id?.trim();
4488
4725
  return {
4489
4726
  project: params?.project,
4490
- userId: params?.user_id?.trim() || defaultMcpUserId(),
4491
- sessionId: params?.session_id?.trim() || cachedMcpSessionId,
4492
- workspacePath: canonicalizeWorkspacePath(params?.path || process.env.WHISPER_WORKSPACE_PATH || process.cwd())
4727
+ userId: params?.user_id?.trim() || defaultMcpUserId({
4728
+ project: params?.project,
4729
+ workspacePath
4730
+ }),
4731
+ sessionId: explicitSession || (options?.include_default_session ? cachedMcpSessionId : void 0),
4732
+ workspacePath
4493
4733
  };
4494
4734
  }
4495
- async function prepareAutomaticQuery(params) {
4496
- if (!runtimeClient) {
4497
- throw new Error("Whisper runtime client unavailable.");
4498
- }
4499
- const scope = resolveMcpScope(params);
4500
- const key = [
4501
- params.project || DEFAULT_PROJECT || "",
4502
- scope.userId,
4503
- scope.sessionId,
4504
- scope.workspacePath || process.cwd(),
4505
- String(params.top_k || 10)
4506
- ].join("|");
4507
- let runtime = runtimeSessions.get(key);
4508
- if (!runtime) {
4509
- runtime = runtimeClient.createAgentRuntime({
4510
- project: params.project,
4511
- userId: scope.userId,
4512
- sessionId: scope.sessionId,
4513
- workspacePath: scope.workspacePath,
4514
- topK: params.top_k,
4515
- clientName: "whisper-mcp"
4516
- });
4517
- runtimeSessions.set(key, runtime);
4518
- }
4519
- return runtime.beforeTurn({
4520
- userMessage: params.query
4521
- });
4522
- }
4523
4735
  function noteAutomaticSourceActivity(params) {
4524
4736
  if (!runtimeClient) return;
4525
4737
  const sourceIds = [...new Set((params.sourceIds || []).map((value) => String(value || "").trim()).filter(Boolean))];
4526
4738
  if (sourceIds.length === 0) return;
4527
- const scope = resolveMcpScope(params);
4739
+ const scope = resolveMcpScope(params, { include_default_session: true });
4528
4740
  const key = [
4529
4741
  params.project || DEFAULT_PROJECT || "",
4530
4742
  scope.userId,
@@ -4556,7 +4768,7 @@ function buildAbstain(args) {
4556
4768
  warnings: args.warnings || [],
4557
4769
  trust_state: args.trust_state,
4558
4770
  recommended_next_calls: sanitizeRecommendedNextCalls(
4559
- args.recommended_next_calls || ["index.workspace_status", "index.workspace_run", "grep", "context.get_relevant"]
4771
+ args.recommended_next_calls || ["index.workspace_status", "index.workspace_run", "fix", "context.query"]
4560
4772
  ),
4561
4773
  diagnostics: {
4562
4774
  claims_evaluated: args.claims_evaluated,
@@ -4611,6 +4823,415 @@ function countCodeFiles(searchPath, maxFiles = 5e3) {
4611
4823
  walk(searchPath);
4612
4824
  return { total, skipped };
4613
4825
  }
4826
+ function computeWorkspaceSnapshot(searchPath, maxFiles = 250) {
4827
+ const files = collectCodeFiles(searchPath, CODE_EXTENSIONS, maxFiles);
4828
+ const entries = files.slice(0, maxFiles).map((filePath) => {
4829
+ try {
4830
+ const st = statSync(filePath);
4831
+ return `${relative(searchPath, filePath)}:${st.mtimeMs}:${st.size}`;
4832
+ } catch {
4833
+ return "";
4834
+ }
4835
+ }).filter(Boolean);
4836
+ const sample = entries.join("|");
4837
+ return {
4838
+ hash: createHash("sha256").update(sample || searchPath).digest("hex").slice(0, 24),
4839
+ entries
4840
+ };
4841
+ }
4842
+ function computeSnapshotChangedFiles(previousEntries, nextEntries) {
4843
+ if (!previousEntries || previousEntries.length === 0) return nextEntries.length;
4844
+ const previousByPath = /* @__PURE__ */ new Map();
4845
+ for (const entry of previousEntries) {
4846
+ const idx = entry.indexOf(":");
4847
+ if (idx <= 0) continue;
4848
+ previousByPath.set(entry.slice(0, idx), entry.slice(idx + 1));
4849
+ }
4850
+ let changed = 0;
4851
+ const seen = /* @__PURE__ */ new Set();
4852
+ for (const entry of nextEntries) {
4853
+ const idx = entry.indexOf(":");
4854
+ if (idx <= 0) continue;
4855
+ const relPath = entry.slice(0, idx);
4856
+ const fingerprint = entry.slice(idx + 1);
4857
+ seen.add(relPath);
4858
+ if (previousByPath.get(relPath) !== fingerprint) changed += 1;
4859
+ }
4860
+ for (const relPath of previousByPath.keys()) {
4861
+ if (!seen.has(relPath)) changed += 1;
4862
+ }
4863
+ return changed;
4864
+ }
4865
+ function shouldTriggerBackgroundRefresh(args) {
4866
+ const nowMs = args.now_ms ?? Date.now();
4867
+ const lastAutoRefreshAt = args.last_auto_refresh_at ? new Date(args.last_auto_refresh_at).getTime() : 0;
4868
+ const debounceMs = 10 * 60 * 1e3;
4869
+ const dueForDebounce = !lastAutoRefreshAt || Number.isNaN(lastAutoRefreshAt) || nowMs - lastAutoRefreshAt >= debounceMs;
4870
+ if (!dueForDebounce) {
4871
+ return { should_refresh: false, trigger_reason: null };
4872
+ }
4873
+ if (args.last_indexed_commit && args.current_head && args.last_indexed_commit !== args.current_head) {
4874
+ return { should_refresh: true, trigger_reason: "head_changed" };
4875
+ }
4876
+ if (args.changed_files >= 20) {
4877
+ return { should_refresh: true, trigger_reason: "delta_files_threshold" };
4878
+ }
4879
+ const ratio = args.indexed_files > 0 ? args.changed_files / args.indexed_files : 0;
4880
+ if (ratio >= 0.02) {
4881
+ return { should_refresh: true, trigger_reason: "delta_ratio_threshold" };
4882
+ }
4883
+ return { should_refresh: false, trigger_reason: null };
4884
+ }
4885
+ async function runBackgroundWorkspaceRefresh(args) {
4886
+ const state = loadState();
4887
+ const workspace = getWorkspaceState(state, args.workspace_id);
4888
+ if (!workspace.index_metadata) workspace.index_metadata = {};
4889
+ const fileStats = countCodeFiles(args.root_path, args.max_files || 1500);
4890
+ const currentHead = getGitHead(args.root_path) || null;
4891
+ const currentSnapshot = computeWorkspaceSnapshot(args.root_path);
4892
+ const gitPendingCount = getGitPendingCount(args.root_path);
4893
+ const changedFiles = gitPendingCount == null ? computeSnapshotChangedFiles(workspace.index_metadata.last_snapshot_entries, currentSnapshot.entries) : gitPendingCount;
4894
+ const lastIndexedCommit = workspace.index_metadata.last_indexed_commit || null;
4895
+ const refreshDecision = shouldTriggerBackgroundRefresh({
4896
+ current_head: currentHead,
4897
+ last_indexed_commit: lastIndexedCommit,
4898
+ changed_files: changedFiles,
4899
+ indexed_files: parsePositiveNumber(workspace.index_metadata.indexed_files, fileStats.total || 1),
4900
+ last_auto_refresh_at: workspace.index_metadata.last_auto_refresh_at || null
4901
+ });
4902
+ const forceFullRequested = args.force_full === true;
4903
+ if (!refreshDecision.should_refresh && !forceFullRequested) {
4904
+ return { refreshed: false, mode: null, reason: null };
4905
+ }
4906
+ const mode = forceFullRequested ? "full" : "incremental";
4907
+ workspace.root_path = args.root_path;
4908
+ workspace.index_metadata.last_indexed_at = (/* @__PURE__ */ new Date()).toISOString();
4909
+ workspace.index_metadata.last_auto_refresh_at = (/* @__PURE__ */ new Date()).toISOString();
4910
+ workspace.index_metadata.last_indexed_commit = currentHead || void 0;
4911
+ workspace.index_metadata.coverage = mode === "full" ? 1 : Math.max(0, Math.min(1, fileStats.total / Math.max(1, args.max_files || 1500)));
4912
+ workspace.index_metadata.indexed_files = fileStats.total;
4913
+ workspace.index_metadata.last_snapshot_hash = currentSnapshot.hash;
4914
+ workspace.index_metadata.last_snapshot_entries = currentSnapshot.entries;
4915
+ if (mode === "full") workspace.index_metadata.last_full_refresh_at = (/* @__PURE__ */ new Date()).toISOString();
4916
+ saveState(state);
4917
+ return {
4918
+ refreshed: true,
4919
+ mode,
4920
+ reason: refreshDecision.trigger_reason || (forceFullRequested ? "manual_full_override" : mode === "full" ? "full_refresh_due" : "manual")
4921
+ };
4922
+ }
4923
+ function buildSourceRepairHint(args) {
4924
+ const remote = parseGitHubRemote(getGitRemoteUrl(args.root_path) || null);
4925
+ if (remote) {
4926
+ const branch = getGitBranch(args.root_path) || "main";
4927
+ return {
4928
+ command: `context.add_source --project ${args.project_ref || "<project>"} --type github --owner ${remote.owner} --repo ${remote.repo} --branch ${branch}`,
4929
+ message: "Attach the matching GitHub source, then rerun fix."
4930
+ };
4931
+ }
4932
+ return {
4933
+ command: `context.add_source --project ${args.project_ref || "<project>"} --type local --path "${args.root_path}"`,
4934
+ message: "Attach the local workspace as a source, then rerun fix."
4935
+ };
4936
+ }
4937
+ async function attemptDeterministicSourceAttach(args) {
4938
+ const remote = parseGitHubRemote(getGitRemoteUrl(args.root_path) || null);
4939
+ if (remote) {
4940
+ const branch = getGitBranch(args.root_path) || "main";
4941
+ const result = await createSourceByType({
4942
+ project: args.project,
4943
+ type: "github",
4944
+ name: `auto-repair:${remote.owner}/${remote.repo}`,
4945
+ owner: remote.owner,
4946
+ repo: remote.repo,
4947
+ branch,
4948
+ auto_index: true
4949
+ });
4950
+ const sourceId = result?.source_id || result?.id;
4951
+ noteAutomaticSourceActivity({
4952
+ project: args.project,
4953
+ sourceIds: sourceId ? [String(sourceId)] : []
4954
+ });
4955
+ return { attached: true, detail: `github:${remote.owner}/${remote.repo}@${branch}` };
4956
+ }
4957
+ if (RUNTIME_MODE !== "remote") {
4958
+ await createSourceByType({
4959
+ project: args.project,
4960
+ type: "local",
4961
+ name: "auto-repair:local-workspace",
4962
+ path: args.root_path,
4963
+ auto_index: true,
4964
+ max_files: 500
4965
+ });
4966
+ return { attached: true, detail: "local_workspace_source_attached" };
4967
+ }
4968
+ return { attached: false, detail: "deterministic_source_inputs_unavailable" };
4969
+ }
4970
+ async function runAutoRepairVerification(args) {
4971
+ if (args.preflight.retrieval_route === "project_repo" && args.preflight.matched_source_ids.length > 0) {
4972
+ try {
4973
+ const queryResult = await queryWithDegradedFallback({
4974
+ project: args.project,
4975
+ query: "how does this project work",
4976
+ top_k: 5,
4977
+ include_memories: false,
4978
+ include_graph: false,
4979
+ source_ids: args.preflight.matched_source_ids
4980
+ });
4981
+ const resultCount = Array.isArray(queryResult.response?.results) ? queryResult.response.results.length : 0;
4982
+ return {
4983
+ passed: resultCount > 0,
4984
+ route: "project_repo",
4985
+ result_count: resultCount,
4986
+ detail: resultCount > 0 ? "project_query_has_results" : "project_query_empty"
4987
+ };
4988
+ } catch (error) {
4989
+ return {
4990
+ passed: false,
4991
+ route: "project_repo",
4992
+ result_count: 0,
4993
+ detail: `project_query_error:${String(error?.message || error)}`
4994
+ };
4995
+ }
4996
+ }
4997
+ if (args.preflight.retrieval_route === "local_workspace_fallback") {
4998
+ try {
4999
+ const local = await runLocalWorkspaceRetrieval({
5000
+ query: "how does this project work",
5001
+ path: args.preflight.trust_state.root_path,
5002
+ project: args.project,
5003
+ top_k: 5
5004
+ });
5005
+ return {
5006
+ passed: local.results.length > 0,
5007
+ route: "local_workspace_fallback",
5008
+ result_count: local.results.length,
5009
+ detail: local.results.length > 0 ? "local_query_has_results" : "local_query_empty"
5010
+ };
5011
+ } catch (error) {
5012
+ return {
5013
+ passed: false,
5014
+ route: "local_workspace_fallback",
5015
+ result_count: 0,
5016
+ detail: `local_query_error:${String(error?.message || error)}`
5017
+ };
5018
+ }
5019
+ }
5020
+ return {
5021
+ passed: false,
5022
+ route: args.preflight.retrieval_route,
5023
+ result_count: 0,
5024
+ detail: "no_verifiable_retrieval_route"
5025
+ };
5026
+ }
5027
+ async function runAutoRepairFlow(input) {
5028
+ const actions_attempted = [];
5029
+ const actions_succeeded = [];
5030
+ const actions_failed = [];
5031
+ const apply = input.apply !== false;
5032
+ const repairScope = input.repair_scope || "safe";
5033
+ const allowRestartHint = input.allow_restart_hint !== false;
5034
+ let restartRequired = false;
5035
+ if (!API_KEY) {
5036
+ return {
5037
+ root_cause_code: "missing_api_key",
5038
+ actions_attempted,
5039
+ actions_succeeded,
5040
+ actions_failed: [...actions_failed, "validate_credentials:missing_api_key"],
5041
+ restart_required: false,
5042
+ verification_passed: false,
5043
+ status_label: "blocked",
5044
+ status_action: "reauth",
5045
+ action_required: true,
5046
+ diagnostics: {
5047
+ message: "WHISPER_API_KEY is required."
5048
+ }
5049
+ };
5050
+ }
5051
+ actions_attempted.push("validate_credentials");
5052
+ actions_succeeded.push("validate_credentials");
5053
+ actions_attempted.push("resolve_project");
5054
+ const resolvedProject = await resolveProjectRef(input.project);
5055
+ if (!resolvedProject) {
5056
+ actions_failed.push("resolve_project:no_project");
5057
+ return {
5058
+ root_cause_code: "no_project_bound",
5059
+ actions_attempted,
5060
+ actions_succeeded,
5061
+ actions_failed,
5062
+ restart_required: false,
5063
+ verification_passed: false,
5064
+ status_label: "needs_user_action",
5065
+ status_action: "add_source",
5066
+ action_required: true,
5067
+ diagnostics: {
5068
+ message: "No project resolved. Set WHISPER_PROJECT or pass project."
5069
+ }
5070
+ };
5071
+ }
5072
+ actions_succeeded.push("resolve_project");
5073
+ actions_attempted.push("resolve_workspace_trust");
5074
+ const beforePreflight = await resolveRepoGroundingPreflight({
5075
+ query: "how does this project work",
5076
+ path: input.path,
5077
+ workspace_id: input.workspace_id,
5078
+ project: resolvedProject
5079
+ });
5080
+ actions_succeeded.push("resolve_workspace_trust");
5081
+ actions_attempted.push("runtime_mode_check");
5082
+ if (allowRestartHint && RUNTIME_MODE === "remote" && (beforePreflight.project_retrieval_readiness === "project_bound_no_repo_source" || beforePreflight.retrieval_readiness === "local_fallback")) {
5083
+ actions_failed.push("runtime_mode_check:remote_mode_blocks_local_repair");
5084
+ restartRequired = true;
5085
+ } else {
5086
+ actions_succeeded.push("runtime_mode_check");
5087
+ }
5088
+ actions_attempted.push("refresh_workspace_index_metadata");
5089
+ let refreshResult = null;
5090
+ try {
5091
+ if (apply) {
5092
+ refreshResult = await runBackgroundWorkspaceRefresh({
5093
+ workspace_id: beforePreflight.trust_state.workspace_id,
5094
+ root_path: beforePreflight.trust_state.root_path,
5095
+ force_full: repairScope === "full"
5096
+ });
5097
+ } else {
5098
+ refreshResult = { refreshed: false, mode: null, reason: "dry_run" };
5099
+ }
5100
+ actions_succeeded.push("refresh_workspace_index_metadata");
5101
+ } catch (error) {
5102
+ actions_failed.push(`refresh_workspace_index_metadata:${String(error?.message || error)}`);
5103
+ }
5104
+ actions_attempted.push("repair_source_readiness");
5105
+ let sourceRepair = {
5106
+ applied: false,
5107
+ attached: false,
5108
+ detail: "not_required",
5109
+ hint: null
5110
+ };
5111
+ if (beforePreflight.project_retrieval_readiness === "project_bound_no_repo_source") {
5112
+ const hint = buildSourceRepairHint({
5113
+ root_path: beforePreflight.trust_state.root_path,
5114
+ project_ref: beforePreflight.trust_state.project_ref
5115
+ });
5116
+ sourceRepair = {
5117
+ applied: apply && repairScope === "full",
5118
+ attached: false,
5119
+ detail: "manual_source_attach_required",
5120
+ hint
5121
+ };
5122
+ if (apply && repairScope === "full") {
5123
+ try {
5124
+ const attach = await attemptDeterministicSourceAttach({
5125
+ project: resolvedProject,
5126
+ root_path: beforePreflight.trust_state.root_path
5127
+ });
5128
+ sourceRepair = {
5129
+ applied: true,
5130
+ attached: attach.attached,
5131
+ detail: attach.detail,
5132
+ hint
5133
+ };
5134
+ if (attach.attached) {
5135
+ actions_succeeded.push("repair_source_readiness");
5136
+ } else {
5137
+ actions_failed.push(`repair_source_readiness:${attach.detail}`);
5138
+ }
5139
+ } catch (error) {
5140
+ actions_failed.push(`repair_source_readiness:${String(error?.message || error)}`);
5141
+ }
5142
+ } else {
5143
+ actions_failed.push("repair_source_readiness:manual_source_attach_required");
5144
+ }
5145
+ } else {
5146
+ actions_succeeded.push("repair_source_readiness");
5147
+ }
5148
+ const afterPreflight = await resolveRepoGroundingPreflight({
5149
+ query: "how does this project work",
5150
+ path: beforePreflight.trust_state.root_path,
5151
+ workspace_id: beforePreflight.trust_state.workspace_id,
5152
+ project: resolvedProject
5153
+ });
5154
+ const semanticStats = getWorkspaceSemanticFailureStats(afterPreflight.trust_state.workspace_id);
5155
+ const trustScore = computeTrustScore({
5156
+ retrieval_readiness: afterPreflight.retrieval_readiness,
5157
+ workspace_health: afterPreflight.trust_state.health,
5158
+ semantic_failure_rate: semanticStats.rate
5159
+ });
5160
+ persistTrustScore(afterPreflight.trust_state.workspace_id, trustScore);
5161
+ const autoHeal = shouldAutoHeal({
5162
+ retrieval_readiness: afterPreflight.retrieval_readiness,
5163
+ trust_score: trustScore,
5164
+ semantic_failure_rate: semanticStats.rate,
5165
+ semantic_attempts: semanticStats.attempts
5166
+ });
5167
+ actions_attempted.push("verification_query");
5168
+ const verification = await runAutoRepairVerification({
5169
+ project: resolvedProject,
5170
+ preflight: afterPreflight
5171
+ });
5172
+ if (verification.passed) actions_succeeded.push("verification_query");
5173
+ else actions_failed.push(`verification_query:${verification.detail}`);
5174
+ const rootCause = resolveAutoRepairRootCause({
5175
+ restart_required: restartRequired,
5176
+ trust_health: beforePreflight.trust_state.health,
5177
+ project_readiness: beforePreflight.project_retrieval_readiness,
5178
+ retrieval_readiness: beforePreflight.retrieval_readiness,
5179
+ semantic_failure_rate: semanticStats.rate,
5180
+ semantic_attempts: semanticStats.attempts
5181
+ });
5182
+ const statusContract = statusContractForReadiness({
5183
+ retrieval_readiness: afterPreflight.retrieval_readiness,
5184
+ workspace_health: afterPreflight.trust_state.health,
5185
+ root_cause_code: rootCause,
5186
+ force_repairing: autoHeal && rootCause === "none"
5187
+ });
5188
+ const verificationPassed = verification.passed;
5189
+ const effectiveStatusAction = statusContract.status_action;
5190
+ const effectiveActionRequired = statusContract.action_required;
5191
+ const transitionImproved = isStrictlyBetterReadiness(
5192
+ beforePreflight.retrieval_readiness,
5193
+ afterPreflight.retrieval_readiness
5194
+ );
5195
+ const explicitBlocker = effectiveActionRequired;
5196
+ const recommendedNextCalls = sanitizeRecommendedNextCalls([
5197
+ ...recommendedNextCallsForRootCause(rootCause),
5198
+ ...afterPreflight.recommended_next_calls
5199
+ ]);
5200
+ return {
5201
+ root_cause_code: rootCause,
5202
+ actions_attempted,
5203
+ actions_succeeded,
5204
+ actions_failed,
5205
+ restart_required: restartRequired,
5206
+ verification_passed: verificationPassed,
5207
+ status_label: statusContract.status_label,
5208
+ status_action: effectiveStatusAction,
5209
+ action_required: effectiveActionRequired,
5210
+ recommended_next_calls: recommendedNextCalls,
5211
+ diagnostics: {
5212
+ runtime_mode: RUNTIME_MODE,
5213
+ workspace_id: afterPreflight.trust_state.workspace_id,
5214
+ root_path: afterPreflight.trust_state.root_path,
5215
+ project_ref: afterPreflight.trust_state.project_ref,
5216
+ retrieval_readiness_before: beforePreflight.retrieval_readiness,
5217
+ retrieval_readiness_after: afterPreflight.retrieval_readiness,
5218
+ project_retrieval_readiness_after: afterPreflight.project_retrieval_readiness,
5219
+ retrieval_readiness_label: retrievalReadinessLabel(afterPreflight.retrieval_readiness),
5220
+ retrieval_readiness_action: retrievalReadinessAction(afterPreflight.retrieval_readiness),
5221
+ trust_score: trustScore,
5222
+ semantic_failure_rate: semanticStats.rate,
5223
+ semantic_attempts: semanticStats.attempts,
5224
+ refresh: refreshResult,
5225
+ source_repair: sourceRepair,
5226
+ verification,
5227
+ auto_heal_triggered: autoHeal,
5228
+ transition_improved: transitionImproved,
5229
+ explicit_blocker: explicitBlocker,
5230
+ recommended_next_calls: recommendedNextCalls,
5231
+ restart_hint: restartRequired ? "Set WHISPER_MCP_MODE=auto in MCP config and restart client." : null
5232
+ }
5233
+ };
5234
+ }
4614
5235
  function toTextResult(payload) {
4615
5236
  return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
4616
5237
  }
@@ -4677,42 +5298,6 @@ function resolveForgetQueryCandidates(rawResults, query) {
4677
5298
  }
4678
5299
  return { memory_ids: [], resolved_by: "none", warning: "Query did not resolve to a reliable memory match. No memories were changed." };
4679
5300
  }
4680
- async function searchMemoriesForContextQuery(args) {
4681
- return whisper.searchMemoriesSOTA({
4682
- project: args.project,
4683
- query: args.query,
4684
- user_id: args.user_id,
4685
- session_id: args.session_id,
4686
- top_k: args.top_k ?? 5,
4687
- include_relations: false,
4688
- include_pending: true
4689
- });
4690
- }
4691
- async function runContextQueryMemoryRescue(args) {
4692
- const scoped = await searchMemoriesForContextQuery(args);
4693
- const scopedResults = formatCanonicalMemoryResults(scoped);
4694
- if (scopedResults.length > 0) {
4695
- return { results: scopedResults, rescue_mode: "scoped" };
4696
- }
4697
- if (args.user_id || args.session_id) {
4698
- return { results: [], rescue_mode: null };
4699
- }
4700
- const broad = await searchMemoriesForContextQuery({
4701
- project: args.project,
4702
- query: args.query,
4703
- top_k: args.top_k
4704
- });
4705
- return { results: formatCanonicalMemoryResults(broad), rescue_mode: formatCanonicalMemoryResults(broad).length > 0 ? "project_broad" : null };
4706
- }
4707
- function renderContextQueryMemoryRescue(args) {
4708
- const lines = args.results.map(
4709
- (result, index) => `${index + 1}. [${result.memory_type || "memory"}, score: ${result.similarity ?? "n/a"}] ${result.content}`
4710
- );
4711
- 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`;
4712
- return `Found ${args.results.length} memory result(s) (${scopeLabel}):
4713
-
4714
- ${lines.join("\n\n")}`;
4715
- }
4716
5301
  function likelyEmbeddingFailure(error) {
4717
5302
  const message = String(error?.message || error || "").toLowerCase();
4718
5303
  return message.includes("embedding") || message.includes("vector") || message.includes("timeout") || message.includes("timed out") || message.includes("temporarily unavailable");
@@ -4733,7 +5318,13 @@ async function queryWithDegradedFallback(params) {
4733
5318
  hybrid: true,
4734
5319
  rerank: true
4735
5320
  });
4736
- return { response, degraded_mode: false };
5321
+ return {
5322
+ response,
5323
+ degraded_mode: false,
5324
+ semantic_status: "ok",
5325
+ fallback_mode: "none",
5326
+ recommended_fixes: []
5327
+ };
4737
5328
  } catch (error) {
4738
5329
  if (!likelyEmbeddingFailure(error)) throw error;
4739
5330
  const response = await whisper.query({
@@ -4756,7 +5347,13 @@ async function queryWithDegradedFallback(params) {
4756
5347
  response,
4757
5348
  degraded_mode: true,
4758
5349
  degraded_reason: "Embedding/graph path unavailable; lexical fallback used.",
4759
- recommendation: "Check embedding service health, then re-run for full hybrid quality."
5350
+ recommendation: "Check embedding service health, then re-run for full hybrid quality.",
5351
+ semantic_status: "failed",
5352
+ fallback_mode: "lexical_backend",
5353
+ recommended_fixes: [
5354
+ "Run fix to auto-repair retrieval health.",
5355
+ "If this persists, re-authenticate and reindex workspace sources."
5356
+ ]
4760
5357
  };
4761
5358
  }
4762
5359
  }
@@ -4868,6 +5465,47 @@ async function runSearchCodeSearch(args) {
4868
5465
  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."
4869
5466
  };
4870
5467
  }
5468
+ function retrievalReadinessLabel(readiness) {
5469
+ if (readiness === "project_repo_source_ready") return "Project retrieval is healthy.";
5470
+ if (readiness === "project_repo_source_stale") return "Project source exists but is not ready.";
5471
+ if (readiness === "project_bound_no_repo_source") return "No matching project source is connected.";
5472
+ if (readiness === "project_unverified") return "Project/source verification failed.";
5473
+ if (readiness === "local_fallback") return "Using local fallback retrieval.";
5474
+ if (readiness === "no_project") return "No project is bound to this workspace.";
5475
+ return "Retrieval readiness is unknown.";
5476
+ }
5477
+ function retrievalReadinessAction(readiness) {
5478
+ if (readiness === "project_repo_source_ready") return "none";
5479
+ if (readiness === "project_repo_source_stale") return "Run fix or index.workspace_run to refresh workspace trust.";
5480
+ if (readiness === "project_bound_no_repo_source") {
5481
+ return RUNTIME_MODE === "remote" ? "Connect a repo source with context.add_source, then run fix." : "Run index.local_scan_ingest or fix.";
5482
+ }
5483
+ if (readiness === "project_unverified") return "Re-authenticate your API key and run fix.";
5484
+ if (readiness === "local_fallback") return "Run fix to restore project-grounded retrieval.";
5485
+ if (readiness === "no_project") return "Provide --project/WHISPER_PROJECT and run fix.";
5486
+ return "Run fix.";
5487
+ }
5488
+ function buildRecommendedFixes(args) {
5489
+ const fixes = /* @__PURE__ */ new Set();
5490
+ if (args.semantic_status === "failed") {
5491
+ fixes.add("Run fix to auto-repair retrieval health.");
5492
+ }
5493
+ if (args.fallback_mode === "lexical_rescue" || args.fallback_mode === "lexical_backend") {
5494
+ fixes.add("Check embedding/vector service health and retry.");
5495
+ }
5496
+ if (args.retrieval_readiness === "project_bound_no_repo_source") {
5497
+ fixes.add(
5498
+ RUNTIME_MODE === "remote" ? "Add a matching source with context.add_source." : "Ingest local files with index.local_scan_ingest."
5499
+ );
5500
+ }
5501
+ if (args.retrieval_readiness === "project_unverified") {
5502
+ fixes.add("Re-authenticate (valid WHISPER_API_KEY) and rerun fix.");
5503
+ }
5504
+ if (args.retrieval_readiness === "project_repo_source_stale") {
5505
+ fixes.add("Refresh workspace metadata via index.workspace_run.");
5506
+ }
5507
+ return [...fixes];
5508
+ }
4871
5509
  function buildLocalWorkspaceDocuments(rootPath, allowedExts, maxFiles) {
4872
5510
  const files = collectCodeFiles(rootPath, allowedExts, maxFiles);
4873
5511
  const documents = [];
@@ -5003,20 +5641,6 @@ function localHitsToEvidence(workspaceId, hits) {
5003
5641
  )
5004
5642
  );
5005
5643
  }
5006
- function renderLocalWorkspaceContext(args) {
5007
- const lines = args.hits.map((hit, index) => {
5008
- const location = hit.line_end && hit.line_end !== hit.line_start ? `${hit.id}:${hit.line_start}-${hit.line_end}` : `${hit.id}:${hit.line_start}`;
5009
- return `${index + 1}. [${location}, score: ${hit.score.toFixed(2)}, mode: ${hit.search_mode}] ${hit.raw_snippet || hit.snippet || hit.id}`;
5010
- });
5011
- 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}):`;
5012
- const warnings = args.warnings.length ? `
5013
-
5014
- [warnings]
5015
- ${args.warnings.join("\n")}` : "";
5016
- return `${header}
5017
-
5018
- ${lines.join("\n\n")}${warnings}`;
5019
- }
5020
5644
  async function runSearchCodeTool(args) {
5021
5645
  const rootPath = canonicalizeWorkspacePath(args.path);
5022
5646
  const allowedExts = args.file_types ? new Set(args.file_types) : CODE_EXTENSIONS;
@@ -5024,12 +5648,21 @@ async function runSearchCodeTool(args) {
5024
5648
  if (files.length === 0) {
5025
5649
  const workspace = await resolveWorkspaceTrust({ path: rootPath });
5026
5650
  const sharedWarnings = [...workspace.warnings];
5651
+ const semanticStatus = "ok";
5652
+ const fallbackMode = "none";
5027
5653
  return {
5028
5654
  tool: "search_code",
5029
5655
  query: args.query,
5030
5656
  path: rootPath,
5031
5657
  results: [],
5032
5658
  count: 0,
5659
+ semantic_status: semanticStatus,
5660
+ fallback_mode: fallbackMode,
5661
+ recommended_fixes: buildRecommendedFixes({
5662
+ retrieval_readiness: workspace.project_ref ? "project_repo_source_ready" : "no_project",
5663
+ semantic_status: semanticStatus,
5664
+ fallback_mode: fallbackMode
5665
+ }),
5033
5666
  warnings: sharedWarnings,
5034
5667
  diagnostics: {
5035
5668
  workspace_id: workspace.workspace_id,
@@ -5058,6 +5691,13 @@ async function runSearchCodeTool(args) {
5058
5691
  path: rootPath,
5059
5692
  results: localRetrieval.results,
5060
5693
  count: localRetrieval.results.length,
5694
+ semantic_status: semanticStatusFromSearchMode(localRetrieval.diagnostics.search_mode),
5695
+ fallback_mode: fallbackModeFromSearchMode(localRetrieval.diagnostics.search_mode),
5696
+ recommended_fixes: buildRecommendedFixes({
5697
+ retrieval_readiness: localRetrieval.workspace.project_ref ? "local_fallback" : "no_project",
5698
+ semantic_status: semanticStatusFromSearchMode(localRetrieval.diagnostics.search_mode),
5699
+ fallback_mode: fallbackModeFromSearchMode(localRetrieval.diagnostics.search_mode)
5700
+ }),
5061
5701
  warnings: localRetrieval.warnings,
5062
5702
  diagnostics: localRetrieval.diagnostics
5063
5703
  };
@@ -5109,7 +5749,9 @@ function saveIngestManifest(manifest) {
5109
5749
  }
5110
5750
  async function ingestLocalPath(params) {
5111
5751
  if (RUNTIME_MODE === "remote") {
5112
- throw new Error("Local ingestion is disabled in remote mode. Set WHISPER_MCP_MODE=auto or local.");
5752
+ throw new Error(
5753
+ "Local ingestion is disabled in remote mode. Use context.add_source for remote projects, or run MCP with WHISPER_MCP_MODE=auto/local."
5754
+ );
5113
5755
  }
5114
5756
  const rootPath = params.path || process.cwd();
5115
5757
  const gate = isPathAllowed(rootPath);
@@ -5309,7 +5951,8 @@ function renderScopedMcpConfig(project, source, client) {
5309
5951
  env: {
5310
5952
  WHISPER_API_KEY: "wctx_...",
5311
5953
  WHISPER_PROJECT: project,
5312
- WHISPER_SCOPE_SOURCE: source
5954
+ WHISPER_SCOPE_SOURCE: source,
5955
+ WHISPER_MCP_MODE: "auto"
5313
5956
  }
5314
5957
  };
5315
5958
  if (client === "json") {
@@ -5389,7 +6032,7 @@ server.tool(
5389
6032
  warnings: Array.from(/* @__PURE__ */ new Set([...trust.warnings, ...repoVerification.warnings])),
5390
6033
  recommended_next_calls: sanitizeRecommendedNextCalls([
5391
6034
  ...trust.recommended_next_calls,
5392
- ...repoVerification.retrieval_readiness === "project_bound_no_repo_source" ? ["context.list_sources", "index.local_scan_ingest"] : []
6035
+ ...getRepoReadinessNextCalls(repoVerification.retrieval_readiness)
5393
6036
  ]),
5394
6037
  freshness: trust.freshness,
5395
6038
  coverage: trust.coverage,
@@ -5406,7 +6049,7 @@ server.tool(
5406
6049
  );
5407
6050
  server.tool(
5408
6051
  "index.workspace_run",
5409
- "Index workspace in full or incremental mode and update index metadata for freshness checks.",
6052
+ "Refresh local workspace index metadata (coverage, commit, freshness) for trust checks. This does not upload files or create backend embeddings.",
5410
6053
  {
5411
6054
  workspace_id: z.string().optional(),
5412
6055
  path: z.string().optional(),
@@ -5415,6 +6058,7 @@ server.tool(
5415
6058
  },
5416
6059
  async ({ workspace_id, path, mode, max_files }) => {
5417
6060
  try {
6061
+ const startAt = Date.now();
5418
6062
  const identity = resolveWorkspaceIdentity({ path, workspace_id });
5419
6063
  const rootPath = identity.root_path;
5420
6064
  const workspaceId = identity.workspace_id;
@@ -5423,11 +6067,17 @@ server.tool(
5423
6067
  const fileStats = countCodeFiles(rootPath, max_files);
5424
6068
  const coverage = Math.max(0, Math.min(1, fileStats.total / Math.max(1, max_files)));
5425
6069
  const now = (/* @__PURE__ */ new Date()).toISOString();
6070
+ const snapshot = computeWorkspaceSnapshot(rootPath);
5426
6071
  workspace.root_path = rootPath;
5427
6072
  workspace.index_metadata = {
5428
6073
  last_indexed_at: now,
5429
6074
  last_indexed_commit: getGitHead(rootPath),
5430
- coverage: mode === "full" ? 1 : coverage
6075
+ coverage: mode === "full" ? 1 : coverage,
6076
+ indexed_files: fileStats.total,
6077
+ last_auto_refresh_at: now,
6078
+ ...mode === "full" ? { last_full_refresh_at: now } : {},
6079
+ last_snapshot_hash: snapshot.hash,
6080
+ last_snapshot_entries: snapshot.entries
5431
6081
  };
5432
6082
  saveState(state);
5433
6083
  const payload = {
@@ -5436,7 +6086,7 @@ server.tool(
5436
6086
  mode,
5437
6087
  indexed_files: fileStats.total,
5438
6088
  skipped_files: fileStats.skipped,
5439
- duration_ms: 0,
6089
+ duration_ms: Date.now() - startAt,
5440
6090
  warnings: fileStats.total === 0 ? ["No code files discovered for indexing."] : [],
5441
6091
  index_metadata: workspace.index_metadata
5442
6092
  };
@@ -5448,7 +6098,7 @@ server.tool(
5448
6098
  );
5449
6099
  server.tool(
5450
6100
  "index.local_scan_ingest",
5451
- "Scan a local folder safely (allowlist + secret filters), ingest changed files, and persist incremental manifest.",
6101
+ "Ingest local files into Whisper backend (chunk/embed/index) and persist incremental manifest. Requires WHISPER_MCP_MODE=auto or local.",
5452
6102
  {
5453
6103
  project: z.string().optional().describe("Project name or slug"),
5454
6104
  path: z.string().optional().describe("Local path to ingest. Defaults to current working directory."),
@@ -5482,9 +6132,264 @@ server.tool(
5482
6132
  }
5483
6133
  }
5484
6134
  );
6135
+ server.tool(
6136
+ "index.auto_repair",
6137
+ "Diagnose and apply deterministic retrieval/setup repairs. This is the canonical zero-stress repair path.",
6138
+ {
6139
+ path: z.string().optional(),
6140
+ workspace_id: z.string().optional(),
6141
+ project: z.string().optional(),
6142
+ apply: z.boolean().optional().default(true),
6143
+ repair_scope: z.enum(["safe", "full"]).optional().default("safe"),
6144
+ allow_restart_hint: z.boolean().optional().default(true)
6145
+ },
6146
+ async ({ path, workspace_id, project, apply, repair_scope, allow_restart_hint }) => {
6147
+ try {
6148
+ const payload = await runAutoRepairFlow({
6149
+ path,
6150
+ workspace_id,
6151
+ project,
6152
+ apply,
6153
+ repair_scope,
6154
+ allow_restart_hint
6155
+ });
6156
+ return toTextResult(payload);
6157
+ } catch (error) {
6158
+ return primaryToolError(error.message, "auto_repair_failed");
6159
+ }
6160
+ }
6161
+ );
6162
+ server.tool(
6163
+ "fix",
6164
+ "Primary alias for zero-stress setup/retrieval repair. Delegates to index.auto_repair.",
6165
+ {
6166
+ path: z.string().optional(),
6167
+ workspace_id: z.string().optional(),
6168
+ project: z.string().optional(),
6169
+ apply: z.boolean().optional().default(true),
6170
+ repair_scope: z.enum(["safe", "full"]).optional().default("safe"),
6171
+ allow_restart_hint: z.boolean().optional().default(true)
6172
+ },
6173
+ async ({ path, workspace_id, project, apply, repair_scope, allow_restart_hint }) => {
6174
+ try {
6175
+ const payload = await runAutoRepairFlow({
6176
+ path,
6177
+ workspace_id,
6178
+ project,
6179
+ apply,
6180
+ repair_scope,
6181
+ allow_restart_hint
6182
+ });
6183
+ return toTextResult(payload);
6184
+ } catch (error) {
6185
+ return primaryToolError(error.message, "fix_failed");
6186
+ }
6187
+ }
6188
+ );
6189
+ async function runUnifiedContextRetrieval(params) {
6190
+ const preflight = await resolveRepoGroundingPreflight({
6191
+ query: params.question,
6192
+ path: params.path,
6193
+ workspace_id: params.workspace_id,
6194
+ project: params.project,
6195
+ chunk_types: params.chunk_types
6196
+ });
6197
+ const retrievalProfile = resolveMcpRetrievalProfile(params.retrieval_profile);
6198
+ const backgroundRefresh = await runBackgroundWorkspaceRefresh({
6199
+ workspace_id: preflight.trust_state.workspace_id,
6200
+ root_path: preflight.trust_state.root_path,
6201
+ force_full: false
6202
+ });
6203
+ if (shouldAbstainForCodebaseTrust({
6204
+ retrievalProfile,
6205
+ codebaseIntent: preflight.repo_grounded,
6206
+ preflight
6207
+ })) {
6208
+ const abstainPayload = buildCodebaseTrustAbstainPayload({
6209
+ query: params.question,
6210
+ preflight,
6211
+ retrievalProfile
6212
+ });
6213
+ return {
6214
+ ...abstainPayload,
6215
+ semantic_status: "failed",
6216
+ fallback_mode: "none",
6217
+ recommended_fixes: buildRecommendedFixes({
6218
+ retrieval_readiness: preflight.retrieval_readiness,
6219
+ semantic_status: "failed",
6220
+ fallback_mode: "none"
6221
+ }),
6222
+ status_label: "blocked",
6223
+ status_action: "reindex",
6224
+ action_required: true,
6225
+ diagnostics: {
6226
+ retrieval_profile: retrievalProfile,
6227
+ background_refresh: backgroundRefresh
6228
+ }
6229
+ };
6230
+ }
6231
+ const resolvedProject = preflight.trust_state.project_ref || await resolveProjectRef(params.project);
6232
+ const scope = resolveMcpScope({
6233
+ project: resolvedProject || params.project,
6234
+ user_id: params.user_id,
6235
+ session_id: params.session_id,
6236
+ path: preflight.trust_state.root_path
6237
+ });
6238
+ const topK = params.top_k ?? 12;
6239
+ let semanticStatus = "ok";
6240
+ let fallbackMode = "none";
6241
+ let contextText = "";
6242
+ let evidence = [];
6243
+ let warnings = [...preflight.warnings];
6244
+ let retrievalReadiness = preflight.retrieval_readiness;
6245
+ let retrievalRoute = preflight.retrieval_route;
6246
+ let latencyMs = 0;
6247
+ if (preflight.retrieval_route === "local_workspace_fallback") {
6248
+ const localRetrieval = await runLocalWorkspaceRetrieval({
6249
+ query: params.question,
6250
+ path: preflight.trust_state.root_path,
6251
+ project: resolvedProject || params.project,
6252
+ top_k: topK
6253
+ });
6254
+ semanticStatus = semanticStatusFromSearchMode(localRetrieval.diagnostics.search_mode);
6255
+ fallbackMode = fallbackModeFromSearchMode(localRetrieval.diagnostics.search_mode);
6256
+ evidence = localHitsToEvidence(preflight.trust_state.workspace_id, localRetrieval.results);
6257
+ contextText = evidence.map((item) => `[${renderCitation(item)}] ${item.snippet || "Relevant context found."}`).join("\n");
6258
+ warnings = Array.from(/* @__PURE__ */ new Set([...warnings, ...localRetrieval.warnings]));
6259
+ retrievalReadiness = "local_fallback";
6260
+ retrievalRoute = "local_workspace_fallback";
6261
+ } else if (!resolvedProject) {
6262
+ contextText = "";
6263
+ evidence = [];
6264
+ retrievalRoute = "none";
6265
+ } else {
6266
+ const queryResult = await queryWithDegradedFallback({
6267
+ project: resolvedProject,
6268
+ query: params.question,
6269
+ top_k: topK,
6270
+ include_memories: preflight.repo_grounded ? false : params.include_memories,
6271
+ include_graph: params.include_graph,
6272
+ session_id: params.session_id,
6273
+ user_id: scope.userId,
6274
+ source_ids: preflight.repo_grounded ? preflight.matched_source_ids : void 0,
6275
+ retrieval_profile: retrievalProfile,
6276
+ include_parent_content: params.include_parent_content
6277
+ });
6278
+ latencyMs = queryResult.response.meta?.latency_ms || 0;
6279
+ semanticStatus = queryResult.semantic_status || "ok";
6280
+ fallbackMode = queryResult.fallback_mode || "none";
6281
+ const rawResults = preflight.repo_grounded ? filterProjectRepoResults(queryResult.response.results || [], preflight.matched_source_ids) : queryResult.response.results || [];
6282
+ if (preflight.repo_grounded && rawResults.length === 0) {
6283
+ const localRetrieval = await runLocalWorkspaceRetrieval({
6284
+ query: params.question,
6285
+ path: preflight.trust_state.root_path,
6286
+ project: resolvedProject,
6287
+ top_k: topK
6288
+ });
6289
+ semanticStatus = semanticStatusFromSearchMode(localRetrieval.diagnostics.search_mode);
6290
+ fallbackMode = fallbackModeFromSearchMode(localRetrieval.diagnostics.search_mode);
6291
+ evidence = localHitsToEvidence(preflight.trust_state.workspace_id, localRetrieval.results);
6292
+ contextText = evidence.map((item) => `[${renderCitation(item)}] ${item.snippet || "Relevant context found."}`).join("\n");
6293
+ warnings = Array.from(/* @__PURE__ */ new Set([...warnings, ...localRetrieval.warnings]));
6294
+ retrievalReadiness = "local_fallback";
6295
+ retrievalRoute = "local_workspace_fallback";
6296
+ } else {
6297
+ contextText = queryResult.response.context || "";
6298
+ evidence = rawResults.map((r) => toEvidenceRef(r, preflight.trust_state.workspace_id, "semantic"));
6299
+ if (queryResult.degraded_mode) {
6300
+ warnings.push(queryResult.degraded_reason || "Lexical backend fallback active.");
6301
+ }
6302
+ retrievalRoute = preflight.repo_grounded ? "project_repo" : "none";
6303
+ }
6304
+ }
6305
+ recordSemanticAttempt(preflight.trust_state.workspace_id, semanticStatus === "failed");
6306
+ const semanticStats = getWorkspaceSemanticFailureStats(preflight.trust_state.workspace_id);
6307
+ const trustScore = computeTrustScore({
6308
+ retrieval_readiness: retrievalReadiness,
6309
+ workspace_health: preflight.trust_state.health,
6310
+ semantic_failure_rate: semanticStats.rate
6311
+ });
6312
+ persistTrustScore(preflight.trust_state.workspace_id, trustScore);
6313
+ const autoHeal = shouldAutoHeal({
6314
+ retrieval_readiness: retrievalReadiness,
6315
+ trust_score: trustScore,
6316
+ semantic_failure_rate: semanticStats.rate,
6317
+ semantic_attempts: semanticStats.attempts
6318
+ });
6319
+ let autoRepairSummary = null;
6320
+ if (autoHeal) {
6321
+ try {
6322
+ autoRepairSummary = await runAutoRepairFlow({
6323
+ path: preflight.trust_state.root_path,
6324
+ workspace_id: preflight.trust_state.workspace_id,
6325
+ project: resolvedProject || params.project,
6326
+ apply: true,
6327
+ repair_scope: "safe",
6328
+ allow_restart_hint: true
6329
+ });
6330
+ warnings.push("Auto-repair executed in safe mode.");
6331
+ } catch (error) {
6332
+ warnings.push(`Auto-repair failed: ${String(error?.message || error)}`);
6333
+ }
6334
+ }
6335
+ const statusContract = statusContractForReadiness({
6336
+ retrieval_readiness: retrievalReadiness,
6337
+ workspace_health: preflight.trust_state.health,
6338
+ semantic_status: semanticStatus,
6339
+ force_repairing: autoHeal
6340
+ });
6341
+ const recommendedFixes = buildRecommendedFixes({
6342
+ retrieval_readiness: retrievalReadiness,
6343
+ semantic_status: semanticStatus,
6344
+ fallback_mode: fallbackMode
6345
+ });
6346
+ const recommendedNextCalls = sanitizeRecommendedNextCalls([
6347
+ ...preflight.recommended_next_calls,
6348
+ ...semanticStatus === "failed" ? ["fix"] : []
6349
+ ]);
6350
+ const payload = {
6351
+ question: params.question,
6352
+ workspace_id: preflight.trust_state.workspace_id,
6353
+ root_path: preflight.trust_state.root_path,
6354
+ trust_state: preflight.trust_state,
6355
+ retrieval_readiness: retrievalReadiness,
6356
+ retrieval_readiness_label: retrievalReadinessLabel(retrievalReadiness),
6357
+ retrieval_readiness_action: retrievalReadinessAction(retrievalReadiness),
6358
+ retrieval_route: retrievalRoute,
6359
+ context: contextText,
6360
+ evidence,
6361
+ total_results: evidence.length,
6362
+ latency_ms: latencyMs,
6363
+ semantic_status: semanticStatus,
6364
+ fallback_mode: fallbackMode,
6365
+ recommended_fixes: recommendedFixes,
6366
+ status_label: statusContract.status_label,
6367
+ status_action: statusContract.status_action,
6368
+ action_required: statusContract.action_required,
6369
+ warnings: Array.from(new Set(warnings)),
6370
+ recommended_next_calls: recommendedNextCalls,
6371
+ diagnostics: {
6372
+ scope: {
6373
+ project: resolvedProject || params.project || null,
6374
+ user_id: scope.userId,
6375
+ session_id: params.session_id || null
6376
+ },
6377
+ retrieval_profile: retrievalProfile,
6378
+ trust_score: trustScore,
6379
+ semantic_failure_rate: semanticStats.rate,
6380
+ auto_heal_triggered: autoHeal,
6381
+ auto_repair: autoRepairSummary,
6382
+ background_refresh: backgroundRefresh
6383
+ }
6384
+ };
6385
+ if (params.include_alias_warning) {
6386
+ payload.warnings = Array.from(/* @__PURE__ */ new Set([...payload.warnings, "deprecated_alias_use_context.query"]));
6387
+ }
6388
+ return payload;
6389
+ }
5485
6390
  server.tool(
5486
6391
  "context.get_relevant",
5487
- "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.",
6392
+ "Default grounded retrieval step for workspace/project questions. Returns ranked evidence with file:line citations and may abstain when workspace/repo trust is not ready.",
5488
6393
  {
5489
6394
  question: z.string().describe("Task/question to retrieve context for"),
5490
6395
  path: z.string().optional().describe("Workspace path. Defaults to current working directory."),
@@ -5500,131 +6405,23 @@ server.tool(
5500
6405
  },
5501
6406
  async ({ question, path, workspace_id, project, top_k, include_memories, include_graph, session_id, user_id, include_parent_content, retrieval_profile }) => {
5502
6407
  try {
5503
- const preflight = await resolveRepoGroundingPreflight({ query: question, path, workspace_id, project });
5504
- const retrievalProfile = resolveMcpRetrievalProfile(retrieval_profile);
5505
- if (shouldAbstainForCodebaseTrust({
5506
- retrievalProfile,
5507
- codebaseIntent: preflight.repo_grounded,
5508
- preflight
5509
- })) {
5510
- return toTextResult(buildCodebaseTrustAbstainPayload({
5511
- query: question,
5512
- preflight,
5513
- retrievalProfile
5514
- }));
5515
- }
5516
- if (preflight.retrieval_route === "local_workspace_fallback") {
5517
- const localRetrieval = await runLocalWorkspaceRetrieval({
5518
- query: question,
5519
- path: preflight.trust_state.root_path,
5520
- project: preflight.trust_state.project_ref || project,
5521
- top_k
5522
- });
5523
- const evidence2 = localHitsToEvidence(preflight.trust_state.workspace_id, localRetrieval.results);
5524
- const payload2 = {
5525
- question,
5526
- workspace_id: preflight.trust_state.workspace_id,
5527
- root_path: preflight.trust_state.root_path,
5528
- identity_source: preflight.trust_state.identity_source,
5529
- trust_state: preflight.trust_state,
5530
- retrieval_readiness: preflight.retrieval_readiness,
5531
- retrieval_route: preflight.retrieval_route,
5532
- grounded_to_workspace: true,
5533
- total_results: evidence2.length,
5534
- context: evidence2.map((item) => `[${renderCitation(item)}] ${item.snippet || "Relevant local workspace context found."}`).join("\n"),
5535
- evidence: evidence2,
5536
- used_context_ids: evidence2.map((item) => item.source_id),
5537
- latency_ms: 0,
5538
- warnings: Array.from(/* @__PURE__ */ new Set([...preflight.warnings, ...localRetrieval.warnings])),
5539
- recommended_next_calls: preflight.recommended_next_calls
5540
- };
5541
- return { content: [{ type: "text", text: JSON.stringify(payload2, null, 2) }] };
5542
- }
5543
- if (!preflight.trust_state.project_ref) {
5544
- const payload2 = {
5545
- question,
5546
- workspace_id: preflight.trust_state.workspace_id,
5547
- root_path: preflight.trust_state.root_path,
5548
- identity_source: preflight.trust_state.identity_source,
5549
- trust_state: preflight.trust_state,
5550
- retrieval_readiness: preflight.retrieval_readiness,
5551
- retrieval_route: "none",
5552
- grounded_to_workspace: false,
5553
- total_results: 0,
5554
- context: "",
5555
- evidence: [],
5556
- used_context_ids: [],
5557
- latency_ms: 0,
5558
- warnings: preflight.warnings,
5559
- recommended_next_calls: preflight.recommended_next_calls
5560
- };
5561
- return { content: [{ type: "text", text: JSON.stringify(payload2, null, 2) }] };
5562
- }
5563
- const queryResult = await queryWithDegradedFallback({
5564
- project: preflight.trust_state.project_ref,
5565
- query: question,
6408
+ const payload = await runUnifiedContextRetrieval({
6409
+ question,
6410
+ path,
6411
+ workspace_id,
6412
+ project,
5566
6413
  top_k,
5567
- include_memories: preflight.repo_grounded ? false : include_memories,
6414
+ include_memories,
5568
6415
  include_graph,
5569
6416
  session_id,
5570
6417
  user_id,
5571
- source_ids: preflight.repo_grounded ? preflight.matched_source_ids : void 0,
5572
- retrieval_profile: retrievalProfile,
5573
- include_parent_content
6418
+ include_parent_content,
6419
+ retrieval_profile,
6420
+ include_alias_warning: true
5574
6421
  });
5575
- const response = queryResult.response;
5576
- const rawResults = preflight.repo_grounded ? filterProjectRepoResults(response.results || [], preflight.matched_source_ids) : response.results || [];
5577
- if (preflight.repo_grounded && rawResults.length === 0) {
5578
- const localRetrieval = await runLocalWorkspaceRetrieval({
5579
- query: question,
5580
- path: preflight.trust_state.root_path,
5581
- project: preflight.trust_state.project_ref,
5582
- top_k
5583
- });
5584
- const evidence2 = localHitsToEvidence(preflight.trust_state.workspace_id, localRetrieval.results);
5585
- const payload2 = {
5586
- question,
5587
- workspace_id: preflight.trust_state.workspace_id,
5588
- root_path: preflight.trust_state.root_path,
5589
- identity_source: preflight.trust_state.identity_source,
5590
- trust_state: preflight.trust_state,
5591
- retrieval_readiness: "local_fallback",
5592
- retrieval_route: "local_workspace_fallback",
5593
- grounded_to_workspace: true,
5594
- total_results: evidence2.length,
5595
- context: evidence2.map((item) => `[${renderCitation(item)}] ${item.snippet || "Relevant local workspace context found."}`).join("\n"),
5596
- evidence: evidence2,
5597
- used_context_ids: evidence2.map((item) => item.source_id),
5598
- latency_ms: 0,
5599
- warnings: Array.from(/* @__PURE__ */ new Set([...preflight.warnings, ...localRetrieval.warnings])),
5600
- recommended_next_calls: preflight.recommended_next_calls
5601
- };
5602
- return { content: [{ type: "text", text: JSON.stringify(payload2, null, 2) }] };
5603
- }
5604
- const evidence = rawResults.map((r) => toEvidenceRef(r, preflight.trust_state.workspace_id, "semantic"));
5605
- const payload = {
5606
- question,
5607
- workspace_id: preflight.trust_state.workspace_id,
5608
- root_path: preflight.trust_state.root_path,
5609
- identity_source: preflight.trust_state.identity_source,
5610
- trust_state: preflight.trust_state,
5611
- retrieval_readiness: preflight.retrieval_readiness,
5612
- retrieval_route: preflight.repo_grounded ? "project_repo" : "none",
5613
- grounded_to_workspace: preflight.repo_grounded ? true : preflight.trust_state.grounded_to_workspace,
5614
- total_results: rawResults.length || response.meta?.total || evidence.length,
5615
- context: response.context || "",
5616
- evidence,
5617
- used_context_ids: rawResults.map((r) => String(r.id)),
5618
- latency_ms: response.meta?.latency_ms || 0,
5619
- degraded_mode: queryResult.degraded_mode,
5620
- degraded_reason: queryResult.degraded_reason,
5621
- recommendation: queryResult.recommendation,
5622
- warnings: preflight.warnings,
5623
- recommended_next_calls: preflight.recommended_next_calls
5624
- };
5625
- return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
6422
+ return toTextResult(payload);
5626
6423
  } catch (error) {
5627
- return { content: [{ type: "text", text: `Error: ${error.message}` }] };
6424
+ return primaryToolError(error.message, "context_get_relevant_failed");
5628
6425
  }
5629
6426
  }
5630
6427
  );
@@ -5831,7 +6628,7 @@ server.tool(
5831
6628
  );
5832
6629
  server.tool(
5833
6630
  "context.query",
5834
- "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.",
6631
+ "Use this when answering from project knowledge rather than general model memory. Retrieves packed context and auto-falls back between repo, local, and memory routes based on trust/readiness.",
5835
6632
  {
5836
6633
  project: z.string().optional().describe("Project name or slug (optional if WHISPER_PROJECT is set)"),
5837
6634
  query: z.string().describe("What are you looking for?"),
@@ -5848,273 +6645,22 @@ server.tool(
5848
6645
  },
5849
6646
  async ({ project, query, path, top_k, chunk_types, include_memories, include_graph, user_id, session_id, max_tokens, include_parent_content, retrieval_profile }) => {
5850
6647
  try {
5851
- const preflight = await resolveRepoGroundingPreflight({ query, path, project, chunk_types });
5852
- const retrievalProfile = resolveMcpRetrievalProfile(retrieval_profile);
5853
- if (shouldAbstainForCodebaseTrust({
5854
- retrievalProfile,
5855
- codebaseIntent: preflight.repo_grounded,
5856
- preflight
5857
- })) {
5858
- return toTextResult(buildCodebaseTrustAbstainPayload({
5859
- query,
5860
- preflight,
5861
- retrievalProfile
5862
- }));
5863
- }
5864
- const resolvedProject = preflight.trust_state.project_ref || await resolveProjectRef(project);
5865
- if (!resolvedProject && !preflight.repo_grounded) {
5866
- return { content: [{ type: "text", text: "Error: No project resolved. Set WHISPER_PROJECT or pass project." }] };
5867
- }
5868
- const scope = resolveMcpScope({ project: resolvedProject, user_id, session_id, path: preflight.trust_state.root_path });
5869
- if (preflight.repo_grounded && preflight.retrieval_route === "local_workspace_fallback") {
5870
- const localRetrieval = await runLocalWorkspaceRetrieval({
5871
- query,
5872
- path: preflight.trust_state.root_path,
5873
- project: resolvedProject || project,
5874
- top_k
5875
- });
5876
- if (localRetrieval.results.length > 0) {
5877
- return {
5878
- content: [{
5879
- type: "text",
5880
- text: renderLocalWorkspaceContext({
5881
- query,
5882
- project_ref: resolvedProject || null,
5883
- scope,
5884
- route: "local_workspace_fallback",
5885
- hits: localRetrieval.results,
5886
- diagnostics: localRetrieval.diagnostics,
5887
- warnings: Array.from(/* @__PURE__ */ new Set([...preflight.warnings, ...localRetrieval.warnings]))
5888
- })
5889
- }]
5890
- };
5891
- }
5892
- }
5893
- if (preflight.repo_grounded && resolvedProject) {
5894
- const queryResult2 = await queryWithDegradedFallback({
5895
- project: resolvedProject,
5896
- query,
5897
- top_k,
5898
- include_memories: false,
5899
- include_graph,
5900
- user_id: user_id || scope.userId,
5901
- session_id: session_id || scope.sessionId,
5902
- source_ids: preflight.matched_source_ids,
5903
- retrieval_profile: retrievalProfile,
5904
- include_parent_content
5905
- });
5906
- const repoResults = filterProjectRepoResults(queryResult2.response.results || [], preflight.matched_source_ids);
5907
- if (repoResults.length > 0) {
5908
- const scopedResponse = { ...queryResult2.response, results: repoResults };
5909
- 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}):
5910
-
5911
- `;
5912
- const suffix2 = [
5913
- `[diagnostics] identity_source=${preflight.trust_state.identity_source} retrieval_readiness=${preflight.retrieval_readiness} retrieval_route=project_repo`,
5914
- queryResult2.degraded_mode ? `[degraded_mode=true] ${queryResult2.degraded_reason}
5915
- Recommendation: ${queryResult2.recommendation}` : "",
5916
- preflight.warnings.length ? `[warnings]
5917
- ${preflight.warnings.join("\n")}` : ""
5918
- ].filter(Boolean).join("\n\n");
5919
- return { content: [{ type: "text", text: `${header2}${scopedResponse.context}${suffix2 ? `
5920
-
5921
- ${suffix2}` : ""}` }] };
5922
- }
5923
- }
5924
- if (preflight.repo_grounded) {
5925
- if (resolvedProject && include_memories !== false) {
5926
- const memoryRescue = await runContextQueryMemoryRescue({
5927
- project: resolvedProject,
5928
- query,
5929
- user_id: user_id ? scope.userId : void 0,
5930
- session_id: session_id ? scope.sessionId : void 0,
5931
- top_k
5932
- });
5933
- if (memoryRescue.results.length && memoryRescue.rescue_mode) {
5934
- return {
5935
- content: [{
5936
- type: "text",
5937
- text: `${renderContextQueryMemoryRescue({
5938
- project: resolvedProject,
5939
- query,
5940
- scope,
5941
- results: memoryRescue.results,
5942
- rescue_mode: memoryRescue.rescue_mode
5943
- })}
5944
-
5945
- [diagnostics]
5946
- retrieval_route=memory_only retrieval_readiness=${preflight.retrieval_readiness} workspace=${preflight.trust_state.workspace_id}`
5947
- }]
5948
- };
5949
- }
5950
- }
5951
- return {
5952
- content: [{
5953
- type: "text",
5954
- text: `No relevant repo-grounded context found.
5955
-
5956
- [diagnostics]
5957
- workspace=${preflight.trust_state.workspace_id} identity_source=${preflight.trust_state.identity_source} retrieval_readiness=${preflight.retrieval_readiness} retrieval_route=${preflight.retrieval_route}`
5958
- }]
5959
- };
5960
- }
5961
- const automaticMode = !preflight.repo_grounded && include_memories !== false && include_graph !== true && !(chunk_types && chunk_types.length > 0) && max_tokens === void 0 && runtimeClient;
5962
- if (automaticMode) {
5963
- try {
5964
- const prepared = await prepareAutomaticQuery({
5965
- project: resolvedProject,
5966
- query,
5967
- top_k,
5968
- user_id,
5969
- session_id,
5970
- path: preflight.trust_state.root_path
5971
- });
5972
- if (!prepared.items.length) {
5973
- const memoryRescue = include_memories !== false ? await runContextQueryMemoryRescue({
5974
- project: resolvedProject,
5975
- query,
5976
- user_id: user_id ? scope.userId : void 0,
5977
- session_id: session_id ? scope.sessionId : void 0,
5978
- top_k
5979
- }) : { results: [], rescue_mode: null };
5980
- if (memoryRescue.results.length && memoryRescue.rescue_mode) {
5981
- return {
5982
- content: [{
5983
- type: "text",
5984
- text: renderContextQueryMemoryRescue({
5985
- project: resolvedProject,
5986
- query,
5987
- scope,
5988
- results: memoryRescue.results,
5989
- rescue_mode: memoryRescue.rescue_mode
5990
- })
5991
- }]
5992
- };
5993
- }
5994
- return { content: [{ type: "text", text: "No relevant context found." }] };
5995
- }
5996
- const warnings = prepared.retrieval.warnings.length ? `
5997
-
5998
- [automatic_runtime]
5999
- ${prepared.retrieval.warnings.join("\n")}` : "";
6000
- const diagnostics = [
6001
- `focused_scope=${prepared.retrieval.focusedScopeApplied}`,
6002
- `fallback_used=${prepared.retrieval.fallbackUsed}`,
6003
- `deduped=${prepared.retrieval.dedupedCount}`,
6004
- `dropped_below_floor=${prepared.retrieval.droppedBelowFloor}`
6005
- ].join(" ");
6006
- const scopeLabel = `project=${prepared.scope.project} user=${prepared.scope.userId} session=${prepared.scope.sessionId}`;
6007
- return {
6008
- content: [{
6009
- type: "text",
6010
- text: `Found ${prepared.items.length} runtime-ranked items (${prepared.retrieval.durationMs}ms, ${scopeLabel}, ${diagnostics}):
6011
-
6012
- ${prepared.context}${warnings}`
6013
- }]
6014
- };
6015
- } catch (error) {
6016
- const automaticWarning = `Automatic runtime unavailable: ${error.message}. Falling back to broad/manual query path.`;
6017
- const queryResult2 = await queryWithDegradedFallback({
6018
- project: resolvedProject,
6019
- query,
6020
- top_k,
6021
- include_memories: include_memories === true,
6022
- include_graph,
6023
- user_id: user_id || scope.userId,
6024
- session_id: session_id || scope.sessionId,
6025
- retrieval_profile: retrievalProfile,
6026
- include_parent_content
6027
- });
6028
- const response2 = queryResult2.response;
6029
- if (response2.results.length === 0) {
6030
- const memoryRescue = include_memories !== false ? await runContextQueryMemoryRescue({
6031
- project: resolvedProject,
6032
- query,
6033
- user_id: user_id ? scope.userId : void 0,
6034
- session_id: session_id ? scope.sessionId : void 0,
6035
- top_k
6036
- }) : { results: [], rescue_mode: null };
6037
- if (memoryRescue.results.length && memoryRescue.rescue_mode) {
6038
- return {
6039
- content: [{
6040
- type: "text",
6041
- text: `${renderContextQueryMemoryRescue({
6042
- project: resolvedProject,
6043
- query,
6044
- scope,
6045
- results: memoryRescue.results,
6046
- rescue_mode: memoryRescue.rescue_mode
6047
- })}
6048
-
6049
- [automatic_runtime]
6050
- ${automaticWarning}`
6051
- }]
6052
- };
6053
- }
6054
- return { content: [{ type: "text", text: `No relevant context found.
6055
-
6056
- [automatic_runtime]
6057
- ${automaticWarning}` }] };
6058
- }
6059
- 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}):
6060
-
6061
- `;
6062
- const suffix2 = queryResult2.degraded_mode ? `
6063
-
6064
- [degraded_mode=true] ${queryResult2.degraded_reason}
6065
- Recommendation: ${queryResult2.recommendation}` : "";
6066
- return { content: [{ type: "text", text: `${header2}${response2.context}
6067
-
6068
- [automatic_runtime]
6069
- ${automaticWarning}${suffix2}` }] };
6070
- }
6071
- }
6072
- const queryResult = await queryWithDegradedFallback({
6073
- project: resolvedProject,
6074
- query,
6648
+ const payload = await runUnifiedContextRetrieval({
6649
+ question: query,
6650
+ path,
6651
+ project,
6075
6652
  top_k,
6076
- include_memories: include_memories === true,
6653
+ include_memories,
6077
6654
  include_graph,
6078
- user_id: user_id || scope.userId,
6079
- session_id: session_id || scope.sessionId,
6080
- retrieval_profile: retrievalProfile,
6081
- include_parent_content
6655
+ user_id,
6656
+ session_id,
6657
+ include_parent_content,
6658
+ retrieval_profile,
6659
+ chunk_types
6082
6660
  });
6083
- const response = queryResult.response;
6084
- if (response.results.length === 0) {
6085
- const memoryRescue = include_memories !== false ? await runContextQueryMemoryRescue({
6086
- project: resolvedProject,
6087
- query,
6088
- user_id: user_id ? scope.userId : void 0,
6089
- session_id: session_id ? scope.sessionId : void 0,
6090
- top_k
6091
- }) : { results: [], rescue_mode: null };
6092
- if (memoryRescue.results.length && memoryRescue.rescue_mode) {
6093
- return {
6094
- content: [{
6095
- type: "text",
6096
- text: renderContextQueryMemoryRescue({
6097
- project: resolvedProject,
6098
- query,
6099
- scope,
6100
- results: memoryRescue.results,
6101
- rescue_mode: memoryRescue.rescue_mode
6102
- })
6103
- }]
6104
- };
6105
- }
6106
- return { content: [{ type: "text", text: "No relevant context found." }] };
6107
- }
6108
- 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}):
6109
-
6110
- `;
6111
- const suffix = queryResult.degraded_mode ? `
6112
-
6113
- [degraded_mode=true] ${queryResult.degraded_reason}
6114
- Recommendation: ${queryResult.recommendation}` : "";
6115
- return { content: [{ type: "text", text: header + response.context + suffix }] };
6661
+ return toTextResult(payload);
6116
6662
  } catch (error) {
6117
- return { content: [{ type: "text", text: `Error: ${error.message}` }] };
6663
+ return primaryToolError(error.message, "context_query_failed");
6118
6664
  }
6119
6665
  }
6120
6666
  );
@@ -6146,11 +6692,24 @@ server.tool(
6146
6692
  const jobId = result?.job_id;
6147
6693
  const mode = result?.mode;
6148
6694
  const semanticStatus = result?.semantic_status;
6149
- const typeLabel = memory_type || "factual";
6150
- const text = mode === "async" || jobId ? `Memory queued (job_id: ${jobId || result.id}, type: ${typeLabel}, user=${scope.userId}, session=${scope.sessionId}).` : `Memory stored (id: ${memoryId}, type: ${typeLabel}, user=${scope.userId}, session=${scope.sessionId}${semanticStatus ? `, semantic_status: ${semanticStatus}` : ""}).`;
6151
- return { content: [{ type: "text", text }] };
6695
+ return primaryToolSuccess({
6696
+ tool: "memory.add",
6697
+ memory_id: memoryId || null,
6698
+ job_id: jobId || null,
6699
+ mode: mode || null,
6700
+ semantic_status: semanticStatus || null,
6701
+ memory_type: memory_type || "factual",
6702
+ queued: mode === "async" || Boolean(jobId),
6703
+ diagnostics: {
6704
+ scope: {
6705
+ project: scope.project || null,
6706
+ user_id: scope.userId,
6707
+ session_id: scope.sessionId || null
6708
+ }
6709
+ }
6710
+ });
6152
6711
  } catch (error) {
6153
- return { content: [{ type: "text", text: `Error: ${error.message}` }] };
6712
+ return primaryToolError(error.message);
6154
6713
  }
6155
6714
  }
6156
6715
  );
@@ -6192,7 +6751,14 @@ server.tool(
6192
6751
  user_id: scope.userId,
6193
6752
  session_id: scope.sessionId,
6194
6753
  results: normalizedResults,
6195
- count: normalizedResults.length
6754
+ count: normalizedResults.length,
6755
+ diagnostics: {
6756
+ scope: {
6757
+ project: scope.project || null,
6758
+ user_id: scope.userId,
6759
+ session_id: scope.sessionId || null
6760
+ }
6761
+ }
6196
6762
  });
6197
6763
  } catch (error) {
6198
6764
  return primaryToolError(error.message);
@@ -6456,11 +7022,12 @@ server.tool(
6456
7022
  },
6457
7023
  async ({ project, query, user_id, session_id, question_date, memory_types, top_k, include_relations }) => {
6458
7024
  try {
7025
+ const scope = resolveMcpScope({ project, user_id, session_id });
6459
7026
  const results = await whisper.searchMemoriesSOTA({
6460
- project,
7027
+ project: scope.project,
6461
7028
  query,
6462
- user_id,
6463
- session_id,
7029
+ user_id: scope.userId,
7030
+ session_id: scope.sessionId,
6464
7031
  question_date,
6465
7032
  memory_types,
6466
7033
  top_k,
@@ -6470,10 +7037,19 @@ server.tool(
6470
7037
  return primaryToolSuccess({
6471
7038
  tool: "memory.search_sota",
6472
7039
  query,
7040
+ user_id: scope.userId,
7041
+ session_id: scope.sessionId || null,
6473
7042
  question_date: question_date || null,
6474
7043
  include_relations,
6475
7044
  results: normalizedResults,
6476
- count: normalizedResults.length
7045
+ count: normalizedResults.length,
7046
+ diagnostics: {
7047
+ scope: {
7048
+ project: scope.project || null,
7049
+ user_id: scope.userId,
7050
+ session_id: scope.sessionId || null
7051
+ }
7052
+ }
6477
7053
  });
6478
7054
  } catch (error) {
6479
7055
  return primaryToolError(error.message);
@@ -7509,7 +8085,10 @@ server.tool(
7509
8085
  context: response.context,
7510
8086
  results: response.results,
7511
8087
  degradedMode: queryResult.degraded_mode,
7512
- degradedReason: queryResult.degraded_reason
8088
+ degradedReason: queryResult.degraded_reason,
8089
+ semanticStatus: queryResult.semantic_status,
8090
+ fallbackMode: queryResult.fallback_mode,
8091
+ recommendedFixes: queryResult.recommended_fixes || []
7513
8092
  }));
7514
8093
  } catch (error) {
7515
8094
  return primaryToolError(error.message);
@@ -7773,7 +8352,16 @@ server.tool(
7773
8352
  },
7774
8353
  async ({ project, content, memory_type, user_id, session_id, agent_id, importance }) => {
7775
8354
  try {
7776
- const result = await whisper.addMemory({ project, content, memory_type, user_id, session_id, agent_id, importance });
8355
+ const scope = resolveMcpScope({ project, user_id, session_id });
8356
+ const result = await whisper.addMemory({
8357
+ project: scope.project,
8358
+ content,
8359
+ memory_type,
8360
+ user_id: scope.userId,
8361
+ session_id: scope.sessionId,
8362
+ agent_id,
8363
+ importance
8364
+ });
7777
8365
  const memoryId = result?.memory_id || (result.mode === "sync" ? result.id : null);
7778
8366
  const jobId = result?.job_id || (result.mode === "async" ? result.id : null);
7779
8367
  return primaryToolSuccess({
@@ -7784,7 +8372,14 @@ server.tool(
7784
8372
  mode: result?.mode || null,
7785
8373
  memory_type,
7786
8374
  stored: result.success === true,
7787
- queued: result?.mode === "async" || Boolean(jobId)
8375
+ queued: result?.mode === "async" || Boolean(jobId),
8376
+ diagnostics: {
8377
+ scope: {
8378
+ project: scope.project || null,
8379
+ user_id: scope.userId,
8380
+ session_id: scope.sessionId || null
8381
+ }
8382
+ }
7788
8383
  });
7789
8384
  } catch (error) {
7790
8385
  return primaryToolError(error.message);
@@ -7820,18 +8415,31 @@ server.tool(
7820
8415
  timestamp
7821
8416
  });
7822
8417
  const resolvedProject = await resolveProjectRef(project);
7823
- const result = await ingestSessionWithSyncFallback({
7824
- project: resolvedProject,
7825
- session_id,
8418
+ const scope = resolveMcpScope({
8419
+ project: resolvedProject || project,
7826
8420
  user_id,
8421
+ session_id
8422
+ });
8423
+ const result = await ingestSessionWithSyncFallback({
8424
+ project: scope.project,
8425
+ session_id: scope.sessionId || session_id,
8426
+ user_id: scope.userId,
7827
8427
  messages: normalizedMessages
7828
8428
  });
7829
8429
  return primaryToolSuccess({
7830
8430
  tool: "record",
7831
- session_id,
8431
+ session_id: scope.sessionId || session_id,
8432
+ user_id: scope.userId,
7832
8433
  messages_recorded: normalizedMessages.length,
7833
8434
  memories_created: result.memories_created,
7834
- relations_created: result.relations_created
8435
+ relations_created: result.relations_created,
8436
+ diagnostics: {
8437
+ scope: {
8438
+ project: scope.project || null,
8439
+ user_id: scope.userId,
8440
+ session_id: scope.sessionId || session_id
8441
+ }
8442
+ }
7835
8443
  });
7836
8444
  } catch (error) {
7837
8445
  return primaryToolError(error.message);
@@ -7962,6 +8570,14 @@ async function main() {
7962
8570
  console.error("Error: WHISPER_API_KEY environment variable is required");
7963
8571
  process.exit(1);
7964
8572
  }
8573
+ const startupDiagnostics = {
8574
+ runtime_mode: RUNTIME_MODE,
8575
+ local_ingest_enabled: RUNTIME_MODE !== "remote",
8576
+ startup_status: RUNTIME_MODE === "remote" ? "degraded_auto_repairing" : "healthy",
8577
+ startup_action: RUNTIME_MODE === "remote" ? "restart_client" : "none",
8578
+ warning: RUNTIME_MODE === "remote" ? "Local ingest is disabled in remote mode. Set WHISPER_MCP_MODE=auto and restart MCP to enable local repair paths." : null
8579
+ };
8580
+ console.error(`[whisper-context-mcp] startup_diagnostics=${JSON.stringify(startupDiagnostics)}`);
7965
8581
  console.error("[whisper-context-mcp] Breaking change: canonical namespaced tool names are active. Run with --print-tool-map for migration mapping.");
7966
8582
  const transport = new StdioServerTransport();
7967
8583
  await server.connect(transport);
@@ -7974,20 +8590,35 @@ export {
7974
8590
  RECOMMENDED_NEXT_CALL_ALLOWLIST,
7975
8591
  RETRIEVAL_READINESS_VALUES,
7976
8592
  RETRIEVAL_ROUTE_VALUES,
8593
+ STATUS_ACTION_VALUES,
8594
+ STATUS_LABEL_VALUES,
7977
8595
  WORKSPACE_HEALTH_VALUES,
7978
8596
  canonicalizeWorkspacePath,
7979
8597
  chooseWorkspaceProjectSource,
7980
8598
  classifyProjectRepoReadiness,
7981
8599
  classifyRepoGroundedQuery,
7982
8600
  classifyWorkspaceHealth,
8601
+ computeSnapshotChangedFiles,
8602
+ computeTrustScore,
7983
8603
  createMcpServer,
7984
8604
  createWhisperMcpClient,
7985
8605
  createWhisperMcpRuntimeClient,
8606
+ defaultMcpUserId,
7986
8607
  extractSignature,
8608
+ fallbackModeFromSearchMode,
8609
+ isStrictlyBetterReadiness,
8610
+ recommendedNextCallsAreAcyclic,
8611
+ recommendedNextCallsForRootCause,
7987
8612
  renderScopedMcpConfig,
7988
8613
  resolveForgetQueryCandidates,
8614
+ resolveMcpScope,
7989
8615
  resolveWorkspaceIdentity,
8616
+ retrievalReadinessRank,
7990
8617
  runSearchCodeSearch,
7991
8618
  sanitizeRecommendedNextCalls,
7992
- shouldAbstainForCodebaseTrust
8619
+ semanticStatusFromSearchMode,
8620
+ shouldAbstainForCodebaseTrust,
8621
+ shouldTriggerBackgroundRefresh,
8622
+ statusActionForRootCause,
8623
+ statusContractForReadiness
7993
8624
  };