@zokizuan/satori-mcp 4.9.1 → 4.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,62 +1,64 @@
1
1
  # @zokizuan/satori-mcp
2
2
 
3
- MCP server for Satori agent-safe semantic code search and indexing.
4
-
5
- ## Features
6
-
7
- - Capability-driven execution via `CapabilityResolver`
8
- - Runtime-first `search_codebase` with explicit `scope`, `resultMode`, `groupBy`, and optional `debug` traces
9
- - Deterministic query-prefix operators in `search_codebase` (`lang:`, `path:`, `-path:`, `must:`, `exclude:`)
10
- - Default grouped-result diversity and auto changed-files ranking (`rankingMode="auto_changed_first"`)
11
- - First-class `call_graph` tool with deterministic node/edge sorting and capability-driven language support (currently TS/JS/Python)
12
- - Sidecar-backed `file_outline` tool for per-file symbol navigation and direct call_graph jump handles
13
- - Snapshot v3 safety with index fingerprints and strict `requires_reindex` access gates
14
- - Deterministic train-in-the-error responses for incompatible or legacy index states
15
- - Query-time exclusion support with `.gitignore`-style matching
16
- - Structured search telemetry logs (`[TELEMETRY]` JSON to `stderr`)
17
- - Zod-first tool schemas converted to MCP JSON Schema for `ListTools`
18
- - Auto-generated tool docs from live tool schemas
19
- - `read_file` line-range retrieval with default large-file truncation guard and optional `mode="annotated"` metadata envelope
20
- - Optional proactive sync watcher mode (debounced filesystem events for explicitly touched roots in the current session)
21
- - Index-time AST scope breadcrumbs (TS/JS/Python) rendered in search output as `🧬 Scope`
22
- - Fingerprint schema `dense_v3`/`hybrid_v3` with hard gate for all pre-v3 indexes
23
-
24
- ## Architecture
3
+ Read-only MCP server for Satori. It gives coding agents six deterministic tools for repo search, symbol navigation, call graph context, bounded file reads, and index lifecycle management.
25
4
 
5
+ ## Install
6
+
7
+ Prefer the CLI installer when possible:
8
+
9
+ ```bash
10
+ npx -y @zokizuan/satori-cli@0.3.2 install --client codex
11
+ npx -y @zokizuan/satori-cli@0.3.2 install --client claude
12
+ npx -y @zokizuan/satori-cli@0.3.2 doctor
26
13
  ```
27
- [MCP Client]
28
- -> [index.ts bootstrap + ListTools/CallTool]
29
- -> [tool registry]
30
- -> [manage_index | search_codebase | call_graph | file_outline | read_file | list_codebases]
31
- -> [ToolContext DI]
32
- -> [CapabilityResolver]
33
- -> [SnapshotManager v3 + access gate]
34
- -> [Context / Vector store / Embedding / Reranker adapters]
14
+
15
+ Manual MCP config:
16
+
17
+ ```json
18
+ {
19
+ "mcpServers": {
20
+ "satori": {
21
+ "command": "npx",
22
+ "args": ["-y", "@zokizuan/satori-mcp@4.10.1"],
23
+ "timeout": 180000,
24
+ "env": {
25
+ "EMBEDDING_PROVIDER": "VoyageAI",
26
+ "EMBEDDING_MODEL": "voyage-4-large",
27
+ "EMBEDDING_OUTPUT_DIMENSION": "1024",
28
+ "VOYAGEAI_API_KEY": "your-api-key",
29
+ "VOYAGEAI_RERANKER_MODEL": "rerank-2.5",
30
+ "MILVUS_ADDRESS": "your-milvus-endpoint",
31
+ "MILVUS_TOKEN": "your-milvus-token"
32
+ }
33
+ }
34
+ }
35
+ }
35
36
  ```
36
37
 
37
- Tool surface is hard-broken to 6 tools. This keeps routing explicit while exposing call-chain traversal and file-level navigation as first-class operations.
38
+ Keep startup timeout at `180000` for first-run package resolution.
39
+
40
+ ## Agent Workflow
38
41
 
39
- ## read_file Behavior
42
+ ```text
43
+ list_codebases
44
+ manage_index action="create" path="/absolute/path/to/repo"
45
+ search_codebase path="/absolute/path/to/repo" query="where is auth refresh handled"
46
+ file_outline path="/absolute/path/to/repo" file="src/auth.ts"
47
+ call_graph path="/absolute/path/to/repo" symbolRef={...} direction="both"
48
+ read_file path="/absolute/path/to/repo/src/auth.ts" start_line=1 end_line=160
49
+ ```
50
+
51
+ Important defaults:
40
52
 
41
- - Supports optional `start_line` and `end_line` (1-based, inclusive)
42
- - When no range is provided and file length exceeds `READ_FILE_MAX_LINES` (default `1000`), output is truncated and includes a continuation hint with `path` and next `start_line`
43
- - Optional `mode="annotated"` returns content plus `outlineStatus`, `outline`, `hasMore`, and reindex hints when sidecar data is unavailable
53
+ - `search_codebase` starts with runtime code, grouped by symbol.
54
+ - `search_codebase` runs freshness checks before returning results.
55
+ - `read_file` is bounded and can return continuation hints.
56
+ - `requires_reindex` means reindex first, then retry the original call.
57
+ - `manage_index action="clear"` is destructive and should be explicit.
44
58
 
45
- ## Proactive Sync
59
+ ## Runtime Requirements
46
60
 
47
- - Enabled by default. Set `MCP_ENABLE_WATCHER=false` to disable
48
- - Debounce window via `MCP_WATCH_DEBOUNCE_MS` (default `5000`)
49
- - Watchers are session-scoped: startup does not watch every indexed codebase, only roots touched by successful index/search/navigation/read flows in the current session
50
- - Watch events reuse the same incremental sync pipeline (`reindexByChange`)
51
- - Ignore control files (`.satoriignore`, root `.gitignore`) trigger no-reindex reconciliation:
52
- - delete indexed paths now ignored by active rules
53
- - incremental sync picks up newly unignored files
54
- - signature checks in `ensureFreshness` keep this working even when watcher events are missed
55
- - Safety gates:
56
- - Watch-triggered sync only runs for `indexed`/`sync_completed` codebases
57
- - Events are dropped for `indexing`, `indexfailed`, and `requires_reindex`
58
- - Ignored/hidden paths are excluded (`node_modules`, `.git`, build artifacts, dotfiles)
59
- - On shutdown (`SIGINT`/`SIGTERM`), watchers are explicitly closed
61
+ Configure an embedding provider and Milvus-compatible backend before indexing. Supported embedding providers are OpenAI, VoyageAI, Gemini, and Ollama. Changing provider, model, dimension, vector store, or schema requires a reindex because those values are part of the index fingerprint.
60
62
 
61
63
  <!-- TOOLS_START -->
62
64
 
@@ -93,7 +95,7 @@ Unified semantic search with runtime-first defaults (start with scope="runtime")
93
95
 
94
96
  ### `call_graph`
95
97
 
96
- Traverse the prebuilt call graph sidecar for callers/callees/bidirectional symbol relationships (language support follows the core callGraphQuery capability set; currently TS/JS/Python).
98
+ Traverse the prebuilt call graph sidecar for callers/callees/bidirectional symbol relationships (language support follows the core callGraphQuery capability set; currently TS/JS/Python). When present, testReferences are static call-graph references from test-like files to returned symbols; they are investigation hints and do not prove runtime coverage, assertion coverage, or that a test executed a path.
97
99
 
98
100
  | Parameter | Type | Required | Default | Description |
99
101
  |---|---|---|---|---|
@@ -139,169 +141,21 @@ No parameters.
139
141
 
140
142
  <!-- TOOLS_END -->
141
143
 
142
- ### `read_file.open_symbol` Fields
143
-
144
- `open_symbol` resolves symbols inside the same file passed in `read_file.path`.
145
-
146
- - `symbolId` (string, optional): deterministic symbol id to resolve in `path`.
147
- - `symbolLabel` (string, optional): exact symbol label to resolve in `path`.
148
- - `start_line` (integer, optional): direct 1-based start line for span-based jump.
149
- - `end_line` (integer, optional): direct 1-based end line (inclusive).
150
-
151
- ## MCP Config Examples
152
-
153
- ### JSON-style (Claude Desktop, Cursor)
154
-
155
- ```json
156
- {
157
- "mcpServers": {
158
- "satori": {
159
- "command": "npx",
160
- "args": ["-y", "@zokizuan/satori-mcp@4.9.1"],
161
- "timeout": 180000,
162
- "env": {
163
- "EMBEDDING_PROVIDER": "VoyageAI",
164
- "EMBEDDING_MODEL": "voyage-4-large",
165
- "EMBEDDING_OUTPUT_DIMENSION": "1024",
166
- "VOYAGEAI_API_KEY": "your-api-key",
167
- "VOYAGEAI_RERANKER_MODEL": "rerank-2.5",
168
- "MILVUS_ADDRESS": "your-milvus-endpoint",
169
- "MILVUS_TOKEN": "your-milvus-token"
170
- }
171
- }
172
- }
173
- }
174
- ```
175
-
176
- ### TOML-style (Claude Code CLI)
177
-
178
- ```toml
179
- [mcp_servers.satori]
180
- command = "npx"
181
- args = ["-y", "@zokizuan/satori-mcp@4.9.1"]
182
- startup_timeout_ms = 180000
183
- env = { EMBEDDING_PROVIDER = "VoyageAI", EMBEDDING_MODEL = "voyage-4-large", EMBEDDING_OUTPUT_DIMENSION = "1024", VOYAGEAI_API_KEY = "your-api-key", VOYAGEAI_RERANKER_MODEL = "rerank-2.5", MILVUS_ADDRESS = "your-milvus-endpoint", MILVUS_TOKEN = "your-milvus-token" }
184
- ```
144
+ ## Notes
185
145
 
186
- `MILVUS_TOKEN` is optional auth for endpoints that require it; local unauthenticated Milvus only needs `MILVUS_ADDRESS`.
146
+ - `open_symbol` resolves exact symbols inside the same file passed to `read_file.path`.
147
+ - `MILVUS_TOKEN` is optional auth; local unauthenticated Milvus only needs `MILVUS_ADDRESS`.
148
+ - MCP startup does not require provider credentials or a live Milvus backend. Provider-backed calls report `MISSING_PROVIDER_CONFIG` when setup is incomplete.
149
+ - `MISSING_PROVIDER_CONFIG` is an active setup failure only when it appears as a tool response `code` or `reason`.
187
150
 
188
- ### Local development (when working on this repo)
189
-
190
- ```json
191
- {
192
- "mcpServers": {
193
- "satori": {
194
- "command": "node",
195
- "args": ["/absolute/path/to/satori/packages/mcp/dist/index.js"],
196
- "timeout": 180000,
197
- "env": {
198
- "EMBEDDING_PROVIDER": "VoyageAI",
199
- "EMBEDDING_MODEL": "voyage-4-large",
200
- "EMBEDDING_OUTPUT_DIMENSION": "1024",
201
- "VOYAGEAI_API_KEY": "your-api-key",
202
- "VOYAGEAI_RERANKER_MODEL": "rerank-2.5",
203
- "MILVUS_ADDRESS": "your-milvus-endpoint",
204
- "MILVUS_TOKEN": "your-milvus-token"
205
- }
206
- }
207
- }
208
- }
209
- ```
210
-
211
- Never commit real API keys/tokens into repo config files.
212
-
213
- ## Run Locally
151
+ ## Local Development
214
152
 
215
153
  ```bash
216
154
  pnpm --filter @zokizuan/satori-mcp start
217
- ```
218
-
219
- ## Shell CLI (`@zokizuan/satori-cli`)
220
-
221
- The shell-first installer/client now lives in a separate package: `@zokizuan/satori-cli`.
222
-
223
- ### Install / Uninstall
224
-
225
- Supported installer targets in Phase 1:
226
- - `codex`
227
- - `claude`
228
- - `all`
229
-
230
- Examples:
231
-
232
- ```bash
233
- npx -y @zokizuan/satori-cli@0.3.0 install --client codex
234
- npx -y @zokizuan/satori-cli@0.3.0 install --client claude
235
- npx -y @zokizuan/satori-cli@0.3.0 install --client all --dry-run
236
- npx -y @zokizuan/satori-cli@0.3.0 uninstall --client codex
237
- npx -y @zokizuan/satori-cli@0.3.0 doctor
238
- ```
239
-
240
- Install and uninstall run before MCP session startup, only touch Satori-managed config, and copy/remove these packaged skills:
241
- - `satori-search`
242
- - `satori-navigation`
243
- - `satori-indexing`
244
-
245
- ### Commands
246
-
247
- ```bash
248
- satori-cli tools list
249
- satori-cli tool call <toolName> --args-json '{"path":"/abs/repo","query":"auth"}'
250
- satori-cli tool call <toolName> --args-file ./args.json
251
- satori-cli tool call <toolName> --args-json @-
252
- satori-cli <toolName> [schema-subset flags]
253
- ```
254
-
255
- Global flags (`--startup-timeout-ms`, `--call-timeout-ms`, `--format`, `--debug`) must appear before the command token.
256
- Example: `satori-cli --debug tools list`.
257
-
258
- ### Output + Exit Contract
259
-
260
- - `stdout`: JSON only
261
- - `stderr`: diagnostics and text summaries
262
- - exit `0`: success
263
- - exit `1`: tool-level error (`isError=true` or structured envelope `status!="ok"`)
264
- - exit `2`: usage/argument/schema-subset errors
265
- - exit `3`: startup/transport/protocol/timeout failures
266
-
267
- ### Wrapper Flag Support
268
-
269
- Wrapper mode (`satori-cli <toolName> ...`) supports a strict subset from reflected `tools/list` schemas:
270
-
271
- - primitive properties (`string|number|integer|boolean`)
272
- - enums of primitives
273
- - arrays of primitives (repeat flags in insertion order)
274
- - object properties only via `--<prop>-json '{...}'`
275
-
276
- Tool-level flags that overlap global names are preserved in wrapper mode once command parsing starts.
277
- Example: `satori-cli search_codebase --path /repo --query auth --debug` forwards `debug=true` to the tool.
278
- For boolean wrapper flags, `--flag` implies `true` and `--flag false` is supported.
279
-
280
- Unsupported schema shapes (for example `oneOf`, `anyOf`, `$ref`, complex arrays, nested expansion) return `E_SCHEMA_UNSUPPORTED` with fallback guidance to `--args-json` / `--args-file`.
281
-
282
- ### Run Mode Semantics
283
-
284
- When spawned by `satori-cli`, server process mode is `SATORI_RUN_MODE=cli`:
285
-
286
- - startup background loops are disabled (`verifyCloudState`, watcher mode, background sync)
287
- - stdio safety hardening is enabled (`stdout` protocol-only, logs to `stderr`)
288
- - tool behavior stays on-demand and uses the same six MCP tools
289
-
290
- `SATORI_CLI_STDOUT_GUARD=drop|redirect` controls accidental non-protocol stdout handling (`drop` default).
291
-
292
- ### Startup vs Provider Setup
293
-
294
- MCP startup does not require provider credentials, network access, or a live Milvus backend. The server should complete `initialize` and expose the six tools with an empty provider environment. Provider-backed calls (`manage_index create|reindex|sync|clear` and `search_codebase`) validate their required environment at call time and return `MISSING_PROVIDER_CONFIG` when setup is incomplete.
295
-
296
- ## Development
297
-
298
- ```bash
299
155
  pnpm --filter @zokizuan/satori-mcp build
300
156
  pnpm --filter @zokizuan/satori-mcp typecheck
301
157
  pnpm --filter @zokizuan/satori-mcp test
302
158
  pnpm --filter @zokizuan/satori-mcp docs:check
303
- pnpm --filter @zokizuan/satori-cli build
304
- pnpm --filter @zokizuan/satori-cli test
305
159
  ```
306
160
 
307
- `build` automatically runs docs generation from tool schemas.
161
+ `build` regenerates the tool reference from live tool schemas.
package/dist/config.js CHANGED
@@ -169,7 +169,7 @@ export function showHelpMessage() {
169
169
  console.log(`
170
170
  Satori MCP Server
171
171
 
172
- Usage: npx -y @zokizuan/satori-mcp@4.9.1 [options]
172
+ Usage: npx -y @zokizuan/satori-mcp@4.10.1 [options]
173
173
 
174
174
  Options:
175
175
  --help, -h Show this help message
@@ -206,16 +206,16 @@ Environment Variables:
206
206
 
207
207
  Examples:
208
208
  # Start MCP server with OpenAI and explicit Milvus address
209
- OPENAI_API_KEY=sk-xxx MILVUS_ADDRESS=localhost:19530 npx -y @zokizuan/satori-mcp@4.9.1
209
+ OPENAI_API_KEY=sk-xxx MILVUS_ADDRESS=localhost:19530 npx -y @zokizuan/satori-mcp@4.10.1
210
210
 
211
211
  # Start MCP server with VoyageAI and specific model
212
- EMBEDDING_PROVIDER=VoyageAI VOYAGEAI_API_KEY=pa-xxx EMBEDDING_MODEL=voyage-4-large MILVUS_ADDRESS=https://your-zilliz-endpoint MILVUS_TOKEN=your-token npx -y @zokizuan/satori-mcp@4.9.1
212
+ EMBEDDING_PROVIDER=VoyageAI VOYAGEAI_API_KEY=pa-xxx EMBEDDING_MODEL=voyage-4-large MILVUS_ADDRESS=https://your-zilliz-endpoint MILVUS_TOKEN=your-token npx -y @zokizuan/satori-mcp@4.10.1
213
213
 
214
214
  # Start MCP server with Gemini and specific model
215
- EMBEDDING_PROVIDER=Gemini GEMINI_API_KEY=xxx EMBEDDING_MODEL=gemini-embedding-001 MILVUS_ADDRESS=https://your-zilliz-endpoint MILVUS_TOKEN=your-token npx -y @zokizuan/satori-mcp@4.9.1
215
+ EMBEDDING_PROVIDER=Gemini GEMINI_API_KEY=xxx EMBEDDING_MODEL=gemini-embedding-001 MILVUS_ADDRESS=https://your-zilliz-endpoint MILVUS_TOKEN=your-token npx -y @zokizuan/satori-mcp@4.10.1
216
216
 
217
217
  # Start MCP server with Ollama and specific model
218
- EMBEDDING_PROVIDER=Ollama EMBEDDING_MODEL=nomic-embed-text MILVUS_ADDRESS=localhost:19530 npx -y @zokizuan/satori-mcp@4.9.1
218
+ EMBEDDING_PROVIDER=Ollama EMBEDDING_MODEL=nomic-embed-text MILVUS_ADDRESS=localhost:19530 npx -y @zokizuan/satori-mcp@4.10.1
219
219
  `);
220
220
  }
221
221
  //# sourceMappingURL=config.js.map
@@ -29,6 +29,20 @@ export interface CallGraphEdge {
29
29
  };
30
30
  confidence: number;
31
31
  }
32
+ export interface CallGraphTestReference {
33
+ file: string;
34
+ symbolId: string;
35
+ symbolLabel?: string;
36
+ span: CallGraphSpan;
37
+ site: {
38
+ file: string;
39
+ startLine: number;
40
+ endLine?: number;
41
+ };
42
+ targetSymbolId: string;
43
+ kind: CallGraphEdgeKind;
44
+ confidence: number;
45
+ }
32
46
  export interface CallGraphNote {
33
47
  type: 'unresolved_edge' | 'dynamic_edge' | 'missing_symbol_metadata';
34
48
  file: string;
@@ -59,6 +73,7 @@ export interface CallGraphResponseSupported {
59
73
  edges: CallGraphEdge[];
60
74
  notes: CallGraphNote[];
61
75
  warnings?: string[];
76
+ testReferences?: CallGraphTestReference[];
62
77
  notesTruncated: boolean;
63
78
  totalNoteCount: number;
64
79
  returnedNoteCount: number;
@@ -95,6 +110,7 @@ export declare class CallGraphSidecarManager {
95
110
  depth: number;
96
111
  limit: number;
97
112
  }): CallGraphQueryResponse;
113
+ private buildTestReferences;
98
114
  private buildGraph;
99
115
  private buildSymbolIndex;
100
116
  private resolveTargetNode;
@@ -112,8 +128,12 @@ export declare class CallGraphSidecarManager {
112
128
  private getSidecarPath;
113
129
  private toRelativePath;
114
130
  private getEdgeKey;
131
+ private getTestReferenceKey;
132
+ private isTestPath;
133
+ private hasPathSegment;
115
134
  private sortNodes;
116
135
  private sortEdges;
136
+ private sortTestReferences;
117
137
  private sortNotes;
118
138
  private compareNullableNumbersAsc;
119
139
  private compareNullableStringsAsc;
@@ -9,6 +9,7 @@ const QUERY_SUPPORTED_EXTENSIONS = new Set(getSupportedExtensionsForCapability('
9
9
  const QUERY_SUPPORTED_LANGUAGE_IDS = getSupportedLanguageIdsForCapability('callGraphQuery');
10
10
  const DEFAULT_IGNORE_PATTERNS = ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**', '**/coverage/**', '**/.next/**'];
11
11
  const DEFAULT_CALL_GRAPH_NOTE_LIMIT = 200;
12
+ const DEFAULT_CALL_GRAPH_TEST_REFERENCE_LIMIT = 50;
12
13
  const CALL_KEYWORDS = new Set([
13
14
  'if', 'for', 'while', 'switch', 'catch', 'return', 'new', 'typeof', 'function', 'class', 'def', 'await', 'with', 'from', 'import',
14
15
  ]);
@@ -208,6 +209,7 @@ export class CallGraphSidecarManager {
208
209
  const notes = relevantNotes.slice(0, this.noteLimit);
209
210
  const notesTruncated = totalNoteCount > notes.length;
210
211
  const warnings = notesTruncated ? ['CALL_GRAPH_NOTES_TRUNCATED'] : undefined;
212
+ const testReferences = this.buildTestReferences(sidecar, nodeById, visited).slice(0, DEFAULT_CALL_GRAPH_TEST_REFERENCE_LIMIT);
211
213
  return {
212
214
  supported: true,
213
215
  direction: options.direction,
@@ -217,6 +219,7 @@ export class CallGraphSidecarManager {
217
219
  edges,
218
220
  notes,
219
221
  warnings,
222
+ ...(testReferences.length > 0 ? { testReferences } : {}),
220
223
  notesTruncated,
221
224
  totalNoteCount,
222
225
  returnedNoteCount: notes.length,
@@ -227,6 +230,37 @@ export class CallGraphSidecarManager {
227
230
  },
228
231
  };
229
232
  }
233
+ buildTestReferences(sidecar, nodeById, targetSymbolIds) {
234
+ const referencesByKey = new Map();
235
+ for (const edge of sidecar.edges) {
236
+ if (!targetSymbolIds.has(edge.dstSymbolId)) {
237
+ continue;
238
+ }
239
+ const source = nodeById.get(edge.srcSymbolId);
240
+ if (!source || !this.isTestPath(source.file)) {
241
+ continue;
242
+ }
243
+ const reference = {
244
+ file: source.file,
245
+ symbolId: source.symbolId,
246
+ symbolLabel: source.symbolLabel,
247
+ span: {
248
+ startLine: source.span.startLine,
249
+ endLine: source.span.endLine,
250
+ },
251
+ site: {
252
+ file: edge.site.file,
253
+ startLine: edge.site.startLine,
254
+ ...(Number.isFinite(edge.site.endLine) ? { endLine: edge.site.endLine } : {}),
255
+ },
256
+ targetSymbolId: edge.dstSymbolId,
257
+ kind: edge.kind,
258
+ confidence: edge.confidence,
259
+ };
260
+ referencesByKey.set(this.getTestReferenceKey(reference), reference);
261
+ }
262
+ return this.sortTestReferences(Array.from(referencesByKey.values()));
263
+ }
230
264
  async buildGraph(codebaseRoot, files) {
231
265
  const nodeById = new Map();
232
266
  const fileCache = new Map();
@@ -569,6 +603,22 @@ export class CallGraphSidecarManager {
569
603
  getEdgeKey(edge) {
570
604
  return `${edge.srcSymbolId}:${edge.dstSymbolId}:${edge.kind}:${edge.site.file}:${edge.site.startLine}`;
571
605
  }
606
+ getTestReferenceKey(reference) {
607
+ return `${reference.symbolId}:${reference.targetSymbolId}:${reference.kind}:${reference.site.file}:${reference.site.startLine}`;
608
+ }
609
+ isTestPath(relativePath) {
610
+ const normalizedPath = relativePath.replace(/\\/g, '/').replace(/^\/+/, '').toLowerCase();
611
+ return this.hasPathSegment(normalizedPath, 'test')
612
+ || this.hasPathSegment(normalizedPath, 'tests')
613
+ || this.hasPathSegment(normalizedPath, '__tests__')
614
+ || /\.test\.[^/]+$/.test(normalizedPath)
615
+ || /\.spec\.[^/]+$/.test(normalizedPath);
616
+ }
617
+ hasPathSegment(normalizedPath, segment) {
618
+ return normalizedPath === segment
619
+ || normalizedPath.startsWith(`${segment}/`)
620
+ || normalizedPath.includes(`/${segment}/`);
621
+ }
572
622
  sortNodes(nodes) {
573
623
  return nodes.sort((a, b) => {
574
624
  const fileCmp = this.compareNullableStringsAsc(a.file, b.file);
@@ -597,6 +647,29 @@ export class CallGraphSidecarManager {
597
647
  return this.compareNullableNumbersAsc(a.site?.startLine, b.site?.startLine);
598
648
  });
599
649
  }
650
+ sortTestReferences(references) {
651
+ return references.sort((a, b) => {
652
+ const fileCmp = this.compareNullableStringsAsc(a.file, b.file);
653
+ if (fileCmp !== 0)
654
+ return fileCmp;
655
+ const startCmp = this.compareNullableNumbersAsc(a.span?.startLine, b.span?.startLine);
656
+ if (startCmp !== 0)
657
+ return startCmp;
658
+ const labelCmp = this.compareNullableStringsAsc(a.symbolLabel, b.symbolLabel);
659
+ if (labelCmp !== 0)
660
+ return labelCmp;
661
+ const symbolCmp = this.compareNullableStringsAsc(a.symbolId, b.symbolId);
662
+ if (symbolCmp !== 0)
663
+ return symbolCmp;
664
+ const targetCmp = this.compareNullableStringsAsc(a.targetSymbolId, b.targetSymbolId);
665
+ if (targetCmp !== 0)
666
+ return targetCmp;
667
+ const siteFileCmp = this.compareNullableStringsAsc(a.site?.file, b.site?.file);
668
+ if (siteFileCmp !== 0)
669
+ return siteFileCmp;
670
+ return this.compareNullableNumbersAsc(a.site?.startLine, b.site?.startLine);
671
+ });
672
+ }
600
673
  sortNotes(notes) {
601
674
  return notes.sort((a, b) => {
602
675
  const fileCmp = this.compareNullableStringsAsc(a.file, b.file);
@@ -111,6 +111,9 @@ export declare class ToolHandlers {
111
111
  private buildCallGraphHint;
112
112
  private sanitizeIndexedRelativeFilePath;
113
113
  private buildNavigationFallback;
114
+ private buildSearchNextActions;
115
+ private buildChangedCodeDebug;
116
+ private buildGeneratedArtifactsVerificationHint;
114
117
  private normalizeRelativeFilePath;
115
118
  private buildRequiresReindexFileOutlinePayload;
116
119
  private getOutlineStatusForLanguage;
@@ -782,11 +782,12 @@ export class ToolHandlers {
782
782
  || normalizedPath.endsWith('.txt');
783
783
  }
784
784
  isGeneratedPath(normalizedPath) {
785
- return normalizedPath.includes('/dist/')
786
- || normalizedPath.includes('/build/')
787
- || normalizedPath.includes('/coverage/')
788
- || normalizedPath.includes('/.next/')
789
- || normalizedPath.includes('/generated/')
785
+ return this.hasPathSegment(normalizedPath, 'dist')
786
+ || this.hasPathSegment(normalizedPath, 'build')
787
+ || this.hasPathSegment(normalizedPath, 'coverage')
788
+ || this.hasPathSegment(normalizedPath, '.next')
789
+ || this.hasPathSegment(normalizedPath, '.output')
790
+ || this.hasPathSegment(normalizedPath, 'generated')
790
791
  || normalizedPath.endsWith('.min.js')
791
792
  || normalizedPath.endsWith('.min.css');
792
793
  }
@@ -821,7 +822,8 @@ export class ToolHandlers {
821
822
  if (this.isGeneratedPath(normalized)
822
823
  || this.hasPathSegment(normalized, 'coverage')
823
824
  || this.hasPathSegment(normalized, 'dist')
824
- || this.hasPathSegment(normalized, 'build'))
825
+ || this.hasPathSegment(normalized, 'build')
826
+ || this.hasPathSegment(normalized, '.output'))
825
827
  return 'generated';
826
828
  if (this.isTestPath(normalized))
827
829
  return 'tests';
@@ -1898,6 +1900,213 @@ export class ToolHandlers {
1898
1900
  }
1899
1901
  return fallback;
1900
1902
  }
1903
+ buildSearchNextActions(codebaseRoot, relativeFilePath, span, callGraphHint, sidecarReadyForOutline) {
1904
+ if (!callGraphHint.supported) {
1905
+ return undefined;
1906
+ }
1907
+ const normalizedFile = this.sanitizeIndexedRelativeFilePath(relativeFilePath);
1908
+ if (!normalizedFile) {
1909
+ return undefined;
1910
+ }
1911
+ const safeStartLine = Number.isFinite(span.startLine) ? Math.max(1, Number(span.startLine)) : 1;
1912
+ const safeEndLine = Number.isFinite(span.endLine) ? Math.max(safeStartLine, Number(span.endLine)) : safeStartLine;
1913
+ const absolutePath = path.resolve(codebaseRoot, normalizedFile);
1914
+ const symbolRef = {
1915
+ ...callGraphHint.symbolRef,
1916
+ file: normalizedFile,
1917
+ span: {
1918
+ startLine: safeStartLine,
1919
+ endLine: safeEndLine,
1920
+ }
1921
+ };
1922
+ const nextActions = {
1923
+ openSymbol: {
1924
+ tool: 'read_file',
1925
+ args: {
1926
+ path: absolutePath,
1927
+ open_symbol: {
1928
+ symbolId: symbolRef.symbolId,
1929
+ ...(symbolRef.symbolLabel ? { symbolLabel: symbolRef.symbolLabel } : {}),
1930
+ start_line: safeStartLine,
1931
+ end_line: safeEndLine,
1932
+ }
1933
+ }
1934
+ },
1935
+ traceCallers: {
1936
+ tool: 'call_graph',
1937
+ args: {
1938
+ path: codebaseRoot,
1939
+ symbolRef,
1940
+ direction: 'callers',
1941
+ depth: 1,
1942
+ limit: 20,
1943
+ }
1944
+ },
1945
+ traceCallees: {
1946
+ tool: 'call_graph',
1947
+ args: {
1948
+ path: codebaseRoot,
1949
+ symbolRef,
1950
+ direction: 'callees',
1951
+ depth: 1,
1952
+ limit: 20,
1953
+ }
1954
+ }
1955
+ };
1956
+ if (sidecarReadyForOutline && this.getOutlineStatusForLanguage(normalizedFile) === 'ok') {
1957
+ nextActions.outlineWindow = {
1958
+ tool: 'file_outline',
1959
+ args: {
1960
+ path: codebaseRoot,
1961
+ file: normalizedFile,
1962
+ start_line: safeStartLine,
1963
+ end_line: safeEndLine,
1964
+ resolveMode: 'outline',
1965
+ }
1966
+ };
1967
+ }
1968
+ return nextActions;
1969
+ }
1970
+ buildChangedCodeDebug(codebaseRoot, changedFilesState) {
1971
+ if (!changedFilesState.available || changedFilesState.files.size === 0) {
1972
+ return undefined;
1973
+ }
1974
+ const loadSidecar = this.callGraphManager?.loadSidecar;
1975
+ if (typeof loadSidecar !== 'function') {
1976
+ return undefined;
1977
+ }
1978
+ const sidecar = loadSidecar.call(this.callGraphManager, codebaseRoot);
1979
+ if (!sidecar || !Array.isArray(sidecar.nodes) || !Array.isArray(sidecar.edges)) {
1980
+ return undefined;
1981
+ }
1982
+ const changedFiles = Array.from(changedFilesState.files)
1983
+ .map((file) => this.normalizeRelativeFilePath(file))
1984
+ .filter((file) => file.length > 0 && !file.startsWith('..') && !path.posix.isAbsolute(file))
1985
+ .sort((a, b) => a.localeCompare(b));
1986
+ const changedFileSet = new Set(changedFiles);
1987
+ const nodeById = new Map();
1988
+ for (const node of sidecar.nodes) {
1989
+ if (node && typeof node.symbolId === 'string') {
1990
+ nodeById.set(node.symbolId, node);
1991
+ }
1992
+ }
1993
+ const changedSymbols = sidecar.nodes
1994
+ .filter((node) => node && typeof node.file === 'string' && changedFileSet.has(this.normalizeRelativeFilePath(node.file)))
1995
+ .map((node) => ({
1996
+ file: this.normalizeRelativeFilePath(node.file),
1997
+ symbolId: String(node.symbolId),
1998
+ ...(typeof node.symbolLabel === 'string' ? { symbolLabel: node.symbolLabel } : {}),
1999
+ span: {
2000
+ startLine: Number.isFinite(node.span?.startLine) ? Number(node.span.startLine) : 1,
2001
+ endLine: Number.isFinite(node.span?.endLine) ? Number(node.span.endLine) : (Number.isFinite(node.span?.startLine) ? Number(node.span.startLine) : 1),
2002
+ }
2003
+ }))
2004
+ .sort((a, b) => {
2005
+ const fileCmp = this.compareNullableStringsAsc(a.file, b.file);
2006
+ if (fileCmp !== 0)
2007
+ return fileCmp;
2008
+ const startCmp = this.compareNullableNumbersAsc(a.span?.startLine, b.span?.startLine);
2009
+ if (startCmp !== 0)
2010
+ return startCmp;
2011
+ const labelCmp = this.compareNullableStringsAsc(a.symbolLabel, b.symbolLabel);
2012
+ if (labelCmp !== 0)
2013
+ return labelCmp;
2014
+ return this.compareNullableStringsAsc(a.symbolId, b.symbolId);
2015
+ });
2016
+ const changedSymbolIds = new Set(changedSymbols.map((symbol) => symbol.symbolId));
2017
+ const directCallers = sidecar.edges
2018
+ .filter((edge) => edge && changedSymbolIds.has(edge.dstSymbolId))
2019
+ .map((edge) => {
2020
+ const caller = nodeById.get(edge.srcSymbolId);
2021
+ if (!caller) {
2022
+ return null;
2023
+ }
2024
+ const startLine = Number.isFinite(caller.span?.startLine) ? Number(caller.span.startLine) : 1;
2025
+ const endLine = Number.isFinite(caller.span?.endLine) ? Number(caller.span.endLine) : startLine;
2026
+ return {
2027
+ targetSymbolId: String(edge.dstSymbolId),
2028
+ file: this.normalizeRelativeFilePath(caller.file),
2029
+ symbolId: String(caller.symbolId),
2030
+ ...(typeof caller.symbolLabel === 'string' ? { symbolLabel: caller.symbolLabel } : {}),
2031
+ span: {
2032
+ startLine,
2033
+ endLine,
2034
+ },
2035
+ site: {
2036
+ file: this.normalizeRelativeFilePath(edge.site?.file || caller.file),
2037
+ startLine: Number.isFinite(edge.site?.startLine) ? Number(edge.site.startLine) : startLine,
2038
+ ...(Number.isFinite(edge.site?.endLine) ? { endLine: Number(edge.site.endLine) } : {}),
2039
+ },
2040
+ kind: edge.kind === 'import' || edge.kind === 'dynamic' ? edge.kind : 'call',
2041
+ confidence: Number.isFinite(edge.confidence) ? Number(edge.confidence) : 0,
2042
+ };
2043
+ })
2044
+ .filter((caller) => Boolean(caller))
2045
+ .sort((a, b) => {
2046
+ const targetCmp = this.compareNullableStringsAsc(a.targetSymbolId, b.targetSymbolId);
2047
+ if (targetCmp !== 0)
2048
+ return targetCmp;
2049
+ const fileCmp = this.compareNullableStringsAsc(a.file, b.file);
2050
+ if (fileCmp !== 0)
2051
+ return fileCmp;
2052
+ const startCmp = this.compareNullableNumbersAsc(a.span?.startLine, b.span?.startLine);
2053
+ if (startCmp !== 0)
2054
+ return startCmp;
2055
+ const labelCmp = this.compareNullableStringsAsc(a.symbolLabel, b.symbolLabel);
2056
+ if (labelCmp !== 0)
2057
+ return labelCmp;
2058
+ const symbolCmp = this.compareNullableStringsAsc(a.symbolId, b.symbolId);
2059
+ if (symbolCmp !== 0)
2060
+ return symbolCmp;
2061
+ return this.compareNullableNumbersAsc(a.site?.startLine, b.site?.startLine);
2062
+ });
2063
+ return {
2064
+ files: changedFiles,
2065
+ symbols: changedSymbols.slice(0, 50),
2066
+ directCallers: directCallers.slice(0, 50),
2067
+ };
2068
+ }
2069
+ buildGeneratedArtifactsVerificationHint(codebaseRoot, results) {
2070
+ const byFile = new Map();
2071
+ for (const result of results) {
2072
+ const normalizedFile = this.sanitizeIndexedRelativeFilePath(result.file);
2073
+ if (!normalizedFile) {
2074
+ continue;
2075
+ }
2076
+ if (this.classifyNoiseCategory(normalizedFile) !== 'generated') {
2077
+ continue;
2078
+ }
2079
+ const safeStartLine = Number.isFinite(result.span.startLine) ? Math.max(1, Number(result.span.startLine)) : 1;
2080
+ const safeEndLine = Number.isFinite(result.span.endLine) ? Math.max(safeStartLine, Number(result.span.endLine)) : safeStartLine;
2081
+ const existing = byFile.get(normalizedFile);
2082
+ byFile.set(normalizedFile, existing
2083
+ ? {
2084
+ startLine: Math.min(existing.startLine, safeStartLine),
2085
+ endLine: Math.max(existing.endLine, safeEndLine),
2086
+ }
2087
+ : { startLine: safeStartLine, endLine: safeEndLine });
2088
+ }
2089
+ const files = Array.from(byFile.keys()).sort((a, b) => a.localeCompare(b)).slice(0, 5);
2090
+ if (files.length === 0) {
2091
+ return undefined;
2092
+ }
2093
+ return {
2094
+ reason: 'generated_outputs_present',
2095
+ message: 'Generated or build output appeared in search context. Source matches do not prove generated output is current; verify the artifact directly when behavior depends on it.',
2096
+ files,
2097
+ nextSteps: files.map((file) => {
2098
+ const span = byFile.get(file);
2099
+ return {
2100
+ tool: 'read_file',
2101
+ args: {
2102
+ path: path.resolve(codebaseRoot, file),
2103
+ start_line: span.startLine,
2104
+ end_line: span.endLine,
2105
+ }
2106
+ };
2107
+ }),
2108
+ };
2109
+ }
1901
2110
  normalizeRelativeFilePath(relativeFilePath) {
1902
2111
  return relativeFilePath.replace(/\\/g, '/').replace(/^\.\/+/, '').trim();
1903
2112
  }
@@ -2976,6 +3185,9 @@ To force rebuild from scratch: call manage_index with {"action":"create","path":
2976
3185
  const changedFilesState = input.rankingMode === 'auto_changed_first'
2977
3186
  ? this.getChangedFilesForCodebase(effectiveRoot)
2978
3187
  : { available: false, files: new Set() };
3188
+ const debugChangedFilesState = input.debug
3189
+ ? (input.rankingMode === 'auto_changed_first' ? changedFilesState : this.getChangedFilesForCodebase(effectiveRoot))
3190
+ : undefined;
2979
3191
  const changedFilesCount = changedFilesState.files.size;
2980
3192
  const changedFilesBoostWithinThreshold = changedFilesCount > 0 && changedFilesCount <= SEARCH_CHANGED_FIRST_MAX_CHANGED_FILES;
2981
3193
  const changedFilesBoostEnabled = changedFilesState.available && changedFilesBoostWithinThreshold;
@@ -3260,6 +3472,9 @@ To force rebuild from scratch: call manage_index with {"action":"create","path":
3260
3472
  multiplier: SEARCH_CHANGED_FIRST_MULTIPLIER,
3261
3473
  boostedCandidates,
3262
3474
  },
3475
+ ...(debugChangedFilesState ? {
3476
+ changedCode: this.buildChangedCodeDebug(effectiveRoot, debugChangedFilesState),
3477
+ } : {}),
3263
3478
  rerank: {
3264
3479
  enabledByPolicy: rerankDecision.enabledByPolicy,
3265
3480
  skippedByScopeDocs: rerankDecision.skippedByScopeDocs,
@@ -3313,13 +3528,22 @@ To force rebuild from scratch: call manage_index with {"action":"create","path":
3313
3528
  } : {})
3314
3529
  }));
3315
3530
  const noiseMitigationHint = this.buildNoiseMitigationHint(effectiveRoot, rawResults.map((result) => result.file));
3531
+ const generatedArtifactsHint = this.buildGeneratedArtifactsVerificationHint(effectiveRoot, rawResults.map((result) => ({
3532
+ file: result.file,
3533
+ span: result.span,
3534
+ })));
3316
3535
  const responseHints = {};
3317
- if (noiseMitigationHint || debugHintBase || proofDebugHint) {
3536
+ if (noiseMitigationHint || debugHintBase || proofDebugHint || generatedArtifactsHint) {
3318
3537
  responseHints.version = 1;
3319
3538
  }
3320
3539
  if (noiseMitigationHint) {
3321
3540
  responseHints.noiseMitigation = noiseMitigationHint;
3322
3541
  }
3542
+ if (generatedArtifactsHint) {
3543
+ responseHints.verification = {
3544
+ generatedArtifacts: generatedArtifactsHint,
3545
+ };
3546
+ }
3323
3547
  if (debugHintBase) {
3324
3548
  responseHints.debugSearch = debugHintBase;
3325
3549
  }
@@ -3393,6 +3617,7 @@ To force rebuild from scratch: call manage_index with {"action":"create","path":
3393
3617
  const groupId = repSymbolId || this.buildFallbackGroupId(representative.result.relativePath, span);
3394
3618
  const callGraphHint = this.buildCallGraphHint(representative.result.relativePath, span, representative.result.language || 'unknown', repSymbolId || undefined, repSymbolLabel || undefined);
3395
3619
  const navigationFallback = this.buildNavigationFallback(effectiveRoot, representative.result.relativePath, span, callGraphHint, sidecarReadyForOutline);
3620
+ const nextActions = this.buildSearchNextActions(effectiveRoot, representative.result.relativePath, span, callGraphHint, sidecarReadyForOutline);
3396
3621
  groupedResults.push({
3397
3622
  kind: "group",
3398
3623
  groupId,
@@ -3407,6 +3632,7 @@ To force rebuild from scratch: call manage_index with {"action":"create","path":
3407
3632
  collapsedChunkCount: group.chunks.length,
3408
3633
  callGraphHint,
3409
3634
  ...(navigationFallback ? { navigationFallback } : {}),
3635
+ ...(nextActions ? { nextActions } : {}),
3410
3636
  preview: truncateContent(String(representative.result.content || ''), 4000),
3411
3637
  __exactLexicalMatch: representative.exactLexicalMatch,
3412
3638
  ...(input.debug ? {
@@ -3446,13 +3672,22 @@ To force rebuild from scratch: call manage_index with {"action":"create","path":
3446
3672
  const diversityApplied = this.applyGroupDiversity(rankedGroupedResults, input.limit, input.groupBy);
3447
3673
  const visibleGroupedResults = diversityApplied.selected;
3448
3674
  const noiseMitigationHint = this.buildNoiseMitigationHint(effectiveRoot, visibleGroupedResults.map((result) => result.file));
3675
+ const generatedArtifactsHint = this.buildGeneratedArtifactsVerificationHint(effectiveRoot, visibleGroupedResults.map((result) => ({
3676
+ file: result.file,
3677
+ span: result.span,
3678
+ })));
3449
3679
  const responseHints = {};
3450
- if (noiseMitigationHint || debugHintBase || proofDebugHint) {
3680
+ if (noiseMitigationHint || debugHintBase || proofDebugHint || generatedArtifactsHint) {
3451
3681
  responseHints.version = 1;
3452
3682
  }
3453
3683
  if (noiseMitigationHint) {
3454
3684
  responseHints.noiseMitigation = noiseMitigationHint;
3455
3685
  }
3686
+ if (generatedArtifactsHint) {
3687
+ responseHints.verification = {
3688
+ generatedArtifacts: generatedArtifactsHint,
3689
+ };
3690
+ }
3456
3691
  if (debugHintBase) {
3457
3692
  responseHints.debugSearch = {
3458
3693
  ...debugHintBase,
@@ -19,6 +19,44 @@ export type CallGraphHint = {
19
19
  supported: false;
20
20
  reason: "missing_symbol" | "unsupported_language";
21
21
  };
22
+ export interface SearchNextActionReadSymbol {
23
+ tool: "read_file";
24
+ args: {
25
+ path: string;
26
+ open_symbol: {
27
+ symbolId: string;
28
+ symbolLabel?: string;
29
+ start_line: number;
30
+ end_line: number;
31
+ };
32
+ };
33
+ }
34
+ export interface SearchNextActionFileOutlineWindow {
35
+ tool: "file_outline";
36
+ args: {
37
+ path: string;
38
+ file: string;
39
+ start_line: number;
40
+ end_line: number;
41
+ resolveMode: "outline";
42
+ };
43
+ }
44
+ export interface SearchNextActionCallGraph {
45
+ tool: "call_graph";
46
+ args: {
47
+ path: string;
48
+ symbolRef: CallGraphSymbolRef;
49
+ direction: "callers" | "callees";
50
+ depth: number;
51
+ limit: number;
52
+ };
53
+ }
54
+ export interface SearchNextActions {
55
+ openSymbol?: SearchNextActionReadSymbol;
56
+ outlineWindow?: SearchNextActionFileOutlineWindow;
57
+ traceCallers?: SearchNextActionCallGraph;
58
+ traceCallees?: SearchNextActionCallGraph;
59
+ }
22
60
  export interface SearchChunkResult {
23
61
  kind: "chunk";
24
62
  file: string;
@@ -57,6 +95,7 @@ export interface SearchGroupResult {
57
95
  collapsedChunkCount: number;
58
96
  callGraphHint: CallGraphHint;
59
97
  navigationFallback?: SearchNavigationFallback;
98
+ nextActions?: SearchNextActions;
60
99
  preview: string;
61
100
  debug?: {
62
101
  representativeChunkCount: number;
@@ -171,6 +210,29 @@ export interface SearchDebugHint {
171
210
  multiplier: number;
172
211
  boostedCandidates: number;
173
212
  };
213
+ changedCode?: {
214
+ files: string[];
215
+ symbols: Array<{
216
+ file: string;
217
+ symbolId: string;
218
+ symbolLabel?: string;
219
+ span: SearchSpan;
220
+ }>;
221
+ directCallers: Array<{
222
+ targetSymbolId: string;
223
+ file: string;
224
+ symbolId: string;
225
+ symbolLabel?: string;
226
+ span: SearchSpan;
227
+ site: {
228
+ file: string;
229
+ startLine: number;
230
+ endLine?: number;
231
+ };
232
+ kind: "call" | "import" | "dynamic";
233
+ confidence: number;
234
+ }>;
235
+ };
174
236
  rerank?: {
175
237
  enabledByPolicy: boolean;
176
238
  skippedByScopeDocs: boolean;
@@ -197,6 +259,14 @@ export interface SearchResponseHints extends Record<string, unknown> {
197
259
  version?: 1;
198
260
  noiseMitigation?: SearchNoiseMitigationHint;
199
261
  debugSearch?: SearchDebugHint;
262
+ verification?: {
263
+ generatedArtifacts?: {
264
+ reason: "generated_outputs_present";
265
+ message: string;
266
+ files: string[];
267
+ nextSteps: SearchNavigationFallbackReadSpan[];
268
+ };
269
+ };
200
270
  }
201
271
  export type NonOkReason = "indexing" | "requires_reindex" | "not_indexed" | "missing_provider_config";
202
272
  interface SearchBaseResponseEnvelope {
@@ -18,7 +18,7 @@ const callGraphInputSchema = z.object({
18
18
  });
19
19
  export const callGraphTool = {
20
20
  name: 'call_graph',
21
- description: () => 'Traverse the prebuilt call graph sidecar for callers/callees/bidirectional symbol relationships (language support follows the core callGraphQuery capability set; currently TS/JS/Python).',
21
+ description: () => 'Traverse the prebuilt call graph sidecar for callers/callees/bidirectional symbol relationships (language support follows the core callGraphQuery capability set; currently TS/JS/Python). When present, testReferences are static call-graph references from test-like files to returned symbols; they are investigation hints and do not prove runtime coverage, assertion coverage, or that a test executed a path.',
22
22
  inputSchemaZod: () => callGraphInputSchema,
23
23
  execute: async (args, ctx) => {
24
24
  const normalizedArgs = (args && typeof args === 'object')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zokizuan/satori-mcp",
3
- "version": "4.9.1",
3
+ "version": "4.10.1",
4
4
  "description": "MCP server for Satori with agent-safe semantic search and indexing",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -14,7 +14,7 @@
14
14
  "ignore": "^7.0.5",
15
15
  "zod": "^3.25.55",
16
16
  "zod-to-json-schema": "^3.25.1",
17
- "@zokizuan/satori-core": "1.5.0"
17
+ "@zokizuan/satori-core": "1.5.2"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@types/node": "^20.0.0",