@zokizuan/satori-mcp 4.9.1 → 4.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -8
- package/dist/config.js +5 -5
- package/dist/core/call-graph.d.ts +20 -0
- package/dist/core/call-graph.js +73 -0
- package/dist/core/handlers.d.ts +3 -0
- package/dist/core/handlers.js +243 -8
- package/dist/core/search-types.d.ts +70 -0
- package/dist/tools/call_graph.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -93,7 +93,7 @@ Unified semantic search with runtime-first defaults (start with scope="runtime")
|
|
|
93
93
|
|
|
94
94
|
### `call_graph`
|
|
95
95
|
|
|
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).
|
|
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). 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
97
|
|
|
98
98
|
| Parameter | Type | Required | Default | Description |
|
|
99
99
|
|---|---|---|---|---|
|
|
@@ -157,7 +157,7 @@ No parameters.
|
|
|
157
157
|
"mcpServers": {
|
|
158
158
|
"satori": {
|
|
159
159
|
"command": "npx",
|
|
160
|
-
"args": ["-y", "@zokizuan/satori-mcp@4.
|
|
160
|
+
"args": ["-y", "@zokizuan/satori-mcp@4.10.0"],
|
|
161
161
|
"timeout": 180000,
|
|
162
162
|
"env": {
|
|
163
163
|
"EMBEDDING_PROVIDER": "VoyageAI",
|
|
@@ -178,7 +178,7 @@ No parameters.
|
|
|
178
178
|
```toml
|
|
179
179
|
[mcp_servers.satori]
|
|
180
180
|
command = "npx"
|
|
181
|
-
args = ["-y", "@zokizuan/satori-mcp@4.
|
|
181
|
+
args = ["-y", "@zokizuan/satori-mcp@4.10.0"]
|
|
182
182
|
startup_timeout_ms = 180000
|
|
183
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
184
|
```
|
|
@@ -230,11 +230,11 @@ Supported installer targets in Phase 1:
|
|
|
230
230
|
Examples:
|
|
231
231
|
|
|
232
232
|
```bash
|
|
233
|
-
npx -y @zokizuan/satori-cli@0.3.
|
|
234
|
-
npx -y @zokizuan/satori-cli@0.3.
|
|
235
|
-
npx -y @zokizuan/satori-cli@0.3.
|
|
236
|
-
npx -y @zokizuan/satori-cli@0.3.
|
|
237
|
-
npx -y @zokizuan/satori-cli@0.3.
|
|
233
|
+
npx -y @zokizuan/satori-cli@0.3.1 install --client codex
|
|
234
|
+
npx -y @zokizuan/satori-cli@0.3.1 install --client claude
|
|
235
|
+
npx -y @zokizuan/satori-cli@0.3.1 install --client all --dry-run
|
|
236
|
+
npx -y @zokizuan/satori-cli@0.3.1 uninstall --client codex
|
|
237
|
+
npx -y @zokizuan/satori-cli@0.3.1 doctor
|
|
238
238
|
```
|
|
239
239
|
|
|
240
240
|
Install and uninstall run before MCP session startup, only touch Satori-managed config, and copy/remove these packaged skills:
|
|
@@ -293,6 +293,8 @@ When spawned by `satori-cli`, server process mode is `SATORI_RUN_MODE=cli`:
|
|
|
293
293
|
|
|
294
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
295
|
|
|
296
|
+
`MISSING_PROVIDER_CONFIG` is an active setup failure only when it appears as a tool response `code` or `reason`. Seeing the string inside `search_codebase` results can simply mean the query matched Satori code that implements the setup error.
|
|
297
|
+
|
|
296
298
|
## Development
|
|
297
299
|
|
|
298
300
|
```bash
|
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.
|
|
172
|
+
Usage: npx -y @zokizuan/satori-mcp@4.10.0 [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.
|
|
209
|
+
OPENAI_API_KEY=sk-xxx MILVUS_ADDRESS=localhost:19530 npx -y @zokizuan/satori-mcp@4.10.0
|
|
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.
|
|
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.0
|
|
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.
|
|
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.0
|
|
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.
|
|
218
|
+
EMBEDDING_PROVIDER=Ollama EMBEDDING_MODEL=nomic-embed-text MILVUS_ADDRESS=localhost:19530 npx -y @zokizuan/satori-mcp@4.10.0
|
|
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;
|
package/dist/core/call-graph.js
CHANGED
|
@@ -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);
|
package/dist/core/handlers.d.ts
CHANGED
|
@@ -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;
|
package/dist/core/handlers.js
CHANGED
|
@@ -782,11 +782,12 @@ export class ToolHandlers {
|
|
|
782
782
|
|| normalizedPath.endsWith('.txt');
|
|
783
783
|
}
|
|
784
784
|
isGeneratedPath(normalizedPath) {
|
|
785
|
-
return
|
|
786
|
-
||
|
|
787
|
-
||
|
|
788
|
-
||
|
|
789
|
-
||
|
|
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 {
|
package/dist/tools/call_graph.js
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "4.10.0",
|
|
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.
|
|
17
|
+
"@zokizuan/satori-core": "1.5.1"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@types/node": "^20.0.0",
|