codesift-mcp 0.1.0 → 0.2.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/LICENSE +66 -21
- package/README.md +402 -56
- package/dist/cli/args.d.ts +2 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +11 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +177 -67
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/help.d.ts +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +157 -0
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/hooks.d.ts +3 -0
- package/dist/cli/hooks.d.ts.map +1 -0
- package/dist/cli/hooks.js +163 -0
- package/dist/cli/hooks.js.map +1 -0
- package/dist/cli/setup.d.ts +25 -0
- package/dist/cli/setup.d.ts.map +1 -0
- package/dist/cli/setup.js +400 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/formatters-shortening.d.ts +7 -0
- package/dist/formatters-shortening.d.ts.map +1 -0
- package/dist/formatters-shortening.js +68 -0
- package/dist/formatters-shortening.js.map +1 -0
- package/dist/formatters.d.ts +314 -0
- package/dist/formatters.d.ts.map +1 -0
- package/dist/formatters.js +396 -0
- package/dist/formatters.js.map +1 -0
- package/dist/instructions.d.ts +6 -0
- package/dist/instructions.d.ts.map +1 -0
- package/dist/instructions.js +72 -0
- package/dist/instructions.js.map +1 -0
- package/dist/lsp/lsp-client.d.ts +21 -0
- package/dist/lsp/lsp-client.d.ts.map +1 -0
- package/dist/lsp/lsp-client.js +122 -0
- package/dist/lsp/lsp-client.js.map +1 -0
- package/dist/lsp/lsp-manager.d.ts +12 -0
- package/dist/lsp/lsp-manager.d.ts.map +1 -0
- package/dist/lsp/lsp-manager.js +82 -0
- package/dist/lsp/lsp-manager.js.map +1 -0
- package/dist/lsp/lsp-servers.d.ts +13 -0
- package/dist/lsp/lsp-servers.d.ts.map +1 -0
- package/dist/lsp/lsp-servers.js +57 -0
- package/dist/lsp/lsp-servers.js.map +1 -0
- package/dist/lsp/lsp-tools.d.ts +67 -0
- package/dist/lsp/lsp-tools.d.ts.map +1 -0
- package/dist/lsp/lsp-tools.js +359 -0
- package/dist/lsp/lsp-tools.js.map +1 -0
- package/dist/parser/extractors/_shared.d.ts +11 -0
- package/dist/parser/extractors/_shared.d.ts.map +1 -0
- package/dist/parser/extractors/_shared.js +38 -0
- package/dist/parser/extractors/_shared.js.map +1 -0
- package/dist/parser/extractors/astro.d.ts +15 -0
- package/dist/parser/extractors/astro.d.ts.map +1 -0
- package/dist/parser/extractors/astro.js +104 -0
- package/dist/parser/extractors/astro.js.map +1 -0
- package/dist/parser/extractors/conversation.d.ts +16 -0
- package/dist/parser/extractors/conversation.d.ts.map +1 -0
- package/dist/parser/extractors/conversation.js +196 -0
- package/dist/parser/extractors/conversation.js.map +1 -0
- package/dist/parser/extractors/go.d.ts.map +1 -1
- package/dist/parser/extractors/go.js +22 -45
- package/dist/parser/extractors/go.js.map +1 -1
- package/dist/parser/extractors/python.d.ts +1 -1
- package/dist/parser/extractors/python.d.ts.map +1 -1
- package/dist/parser/extractors/python.js +19 -50
- package/dist/parser/extractors/python.js.map +1 -1
- package/dist/parser/extractors/rust.d.ts +1 -1
- package/dist/parser/extractors/rust.d.ts.map +1 -1
- package/dist/parser/extractors/rust.js +7 -34
- package/dist/parser/extractors/rust.js.map +1 -1
- package/dist/parser/extractors/typescript.d.ts +1 -1
- package/dist/parser/extractors/typescript.d.ts.map +1 -1
- package/dist/parser/extractors/typescript.js +99 -68
- package/dist/parser/extractors/typescript.js.map +1 -1
- package/dist/parser/parser-manager.d.ts.map +1 -1
- package/dist/parser/parser-manager.js +12 -2
- package/dist/parser/parser-manager.js.map +1 -1
- package/dist/parser/symbol-extractor.d.ts +2 -0
- package/dist/parser/symbol-extractor.d.ts.map +1 -1
- package/dist/parser/symbol-extractor.js +2 -0
- package/dist/parser/symbol-extractor.js.map +1 -1
- package/dist/register-tools.d.ts +127 -0
- package/dist/register-tools.d.ts.map +1 -0
- package/dist/register-tools.js +1453 -0
- package/dist/register-tools.js.map +1 -0
- package/dist/retrieval/codebase-retrieval.d.ts +4 -26
- package/dist/retrieval/codebase-retrieval.d.ts.map +1 -1
- package/dist/retrieval/codebase-retrieval.js +105 -403
- package/dist/retrieval/codebase-retrieval.js.map +1 -1
- package/dist/retrieval/retrieval-constants.d.ts +27 -0
- package/dist/retrieval/retrieval-constants.d.ts.map +1 -0
- package/dist/retrieval/retrieval-constants.js +27 -0
- package/dist/retrieval/retrieval-constants.js.map +1 -0
- package/dist/retrieval/retrieval-schemas.d.ts +107 -0
- package/dist/retrieval/retrieval-schemas.d.ts.map +1 -0
- package/dist/retrieval/retrieval-schemas.js +102 -0
- package/dist/retrieval/retrieval-schemas.js.map +1 -0
- package/dist/retrieval/retrieval-utils.d.ts +40 -0
- package/dist/retrieval/retrieval-utils.d.ts.map +1 -0
- package/dist/retrieval/retrieval-utils.js +139 -0
- package/dist/retrieval/retrieval-utils.js.map +1 -0
- package/dist/retrieval/semantic-handlers.d.ts +8 -0
- package/dist/retrieval/semantic-handlers.d.ts.map +1 -0
- package/dist/retrieval/semantic-handlers.js +152 -0
- package/dist/retrieval/semantic-handlers.js.map +1 -0
- package/dist/search/bm25.d.ts +6 -1
- package/dist/search/bm25.d.ts.map +1 -1
- package/dist/search/bm25.js +95 -32
- package/dist/search/bm25.js.map +1 -1
- package/dist/search/chunker.d.ts +10 -0
- package/dist/search/chunker.d.ts.map +1 -1
- package/dist/search/chunker.js +63 -11
- package/dist/search/chunker.js.map +1 -1
- package/dist/search/reranker.d.ts +15 -0
- package/dist/search/reranker.d.ts.map +1 -0
- package/dist/search/reranker.js +126 -0
- package/dist/search/reranker.js.map +1 -0
- package/dist/search/semantic.d.ts +1 -1
- package/dist/search/semantic.d.ts.map +1 -1
- package/dist/search/semantic.js +40 -45
- package/dist/search/semantic.js.map +1 -1
- package/dist/server-helpers.d.ts +29 -0
- package/dist/server-helpers.d.ts.map +1 -0
- package/dist/server-helpers.js +312 -0
- package/dist/server-helpers.js.map +1 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +11 -271
- package/dist/server.js.map +1 -1
- package/dist/storage/_shared.d.ts +9 -0
- package/dist/storage/_shared.d.ts.map +1 -0
- package/dist/storage/_shared.js +26 -0
- package/dist/storage/_shared.js.map +1 -0
- package/dist/storage/chunk-store.d.ts.map +1 -1
- package/dist/storage/chunk-store.js +23 -63
- package/dist/storage/chunk-store.js.map +1 -1
- package/dist/storage/embedding-store.d.ts +6 -3
- package/dist/storage/embedding-store.d.ts.map +1 -1
- package/dist/storage/embedding-store.js +54 -30
- package/dist/storage/embedding-store.js.map +1 -1
- package/dist/storage/graph-store.d.ts +48 -0
- package/dist/storage/graph-store.d.ts.map +1 -0
- package/dist/storage/graph-store.js +52 -0
- package/dist/storage/graph-store.js.map +1 -0
- package/dist/storage/index-store.d.ts +5 -0
- package/dist/storage/index-store.d.ts.map +1 -1
- package/dist/storage/index-store.js +28 -16
- package/dist/storage/index-store.js.map +1 -1
- package/dist/storage/registry.d.ts +4 -0
- package/dist/storage/registry.d.ts.map +1 -1
- package/dist/storage/registry.js +16 -16
- package/dist/storage/registry.js.map +1 -1
- package/dist/storage/usage-stats.d.ts +6 -0
- package/dist/storage/usage-stats.d.ts.map +1 -1
- package/dist/storage/usage-stats.js +59 -11
- package/dist/storage/usage-stats.js.map +1 -1
- package/dist/storage/usage-tracker.d.ts +3 -0
- package/dist/storage/usage-tracker.d.ts.map +1 -1
- package/dist/storage/usage-tracker.js +50 -132
- package/dist/storage/usage-tracker.js.map +1 -1
- package/dist/storage/watcher.d.ts +2 -1
- package/dist/storage/watcher.d.ts.map +1 -1
- package/dist/storage/watcher.js +16 -16
- package/dist/storage/watcher.js.map +1 -1
- package/dist/tools/ast-query-tools.d.ts +29 -0
- package/dist/tools/ast-query-tools.d.ts.map +1 -0
- package/dist/tools/ast-query-tools.js +110 -0
- package/dist/tools/ast-query-tools.js.map +1 -0
- package/dist/tools/boundary-tools.d.ts +31 -0
- package/dist/tools/boundary-tools.d.ts.map +1 -0
- package/dist/tools/boundary-tools.js +62 -0
- package/dist/tools/boundary-tools.js.map +1 -0
- package/dist/tools/clone-tools.d.ts +35 -0
- package/dist/tools/clone-tools.d.ts.map +1 -0
- package/dist/tools/clone-tools.js +181 -0
- package/dist/tools/clone-tools.js.map +1 -0
- package/dist/tools/community-tools.d.ts +23 -0
- package/dist/tools/community-tools.d.ts.map +1 -0
- package/dist/tools/community-tools.js +297 -0
- package/dist/tools/community-tools.js.map +1 -0
- package/dist/tools/complexity-tools.d.ts +34 -0
- package/dist/tools/complexity-tools.d.ts.map +1 -0
- package/dist/tools/complexity-tools.js +135 -0
- package/dist/tools/complexity-tools.js.map +1 -0
- package/dist/tools/context-tools.d.ts +44 -3
- package/dist/tools/context-tools.d.ts.map +1 -1
- package/dist/tools/context-tools.js +329 -99
- package/dist/tools/context-tools.js.map +1 -1
- package/dist/tools/conversation-tools.d.ts +107 -0
- package/dist/tools/conversation-tools.d.ts.map +1 -0
- package/dist/tools/conversation-tools.js +419 -0
- package/dist/tools/conversation-tools.js.map +1 -0
- package/dist/tools/coordinator-tools.d.ts +73 -0
- package/dist/tools/coordinator-tools.d.ts.map +1 -0
- package/dist/tools/coordinator-tools.js +153 -0
- package/dist/tools/coordinator-tools.js.map +1 -0
- package/dist/tools/cross-repo-tools.d.ts +43 -0
- package/dist/tools/cross-repo-tools.d.ts.map +1 -0
- package/dist/tools/cross-repo-tools.js +55 -0
- package/dist/tools/cross-repo-tools.js.map +1 -0
- package/dist/tools/diff-tools.d.ts +4 -1
- package/dist/tools/diff-tools.d.ts.map +1 -1
- package/dist/tools/diff-tools.js +23 -5
- package/dist/tools/diff-tools.js.map +1 -1
- package/dist/tools/frequency-tools.d.ts +46 -0
- package/dist/tools/frequency-tools.d.ts.map +1 -0
- package/dist/tools/frequency-tools.js +184 -0
- package/dist/tools/frequency-tools.js.map +1 -0
- package/dist/tools/generate-tools.d.ts.map +1 -1
- package/dist/tools/generate-tools.js +13 -2
- package/dist/tools/generate-tools.js.map +1 -1
- package/dist/tools/graph-tools.d.ts +44 -11
- package/dist/tools/graph-tools.d.ts.map +1 -1
- package/dist/tools/graph-tools.js +147 -104
- package/dist/tools/graph-tools.js.map +1 -1
- package/dist/tools/hotspot-tools.d.ts +24 -0
- package/dist/tools/hotspot-tools.d.ts.map +1 -0
- package/dist/tools/hotspot-tools.js +122 -0
- package/dist/tools/hotspot-tools.js.map +1 -0
- package/dist/tools/impact-tools.d.ts +13 -0
- package/dist/tools/impact-tools.d.ts.map +1 -0
- package/dist/tools/impact-tools.js +238 -0
- package/dist/tools/impact-tools.js.map +1 -0
- package/dist/tools/index-tools.d.ts +44 -3
- package/dist/tools/index-tools.d.ts.map +1 -1
- package/dist/tools/index-tools.js +530 -222
- package/dist/tools/index-tools.js.map +1 -1
- package/dist/tools/memory-tools.d.ts +35 -0
- package/dist/tools/memory-tools.d.ts.map +1 -0
- package/dist/tools/memory-tools.js +229 -0
- package/dist/tools/memory-tools.js.map +1 -0
- package/dist/tools/outline-tools.d.ts +24 -13
- package/dist/tools/outline-tools.d.ts.map +1 -1
- package/dist/tools/outline-tools.js +113 -87
- package/dist/tools/outline-tools.js.map +1 -1
- package/dist/tools/pattern-tools.d.ts +32 -0
- package/dist/tools/pattern-tools.d.ts.map +1 -0
- package/dist/tools/pattern-tools.js +116 -0
- package/dist/tools/pattern-tools.js.map +1 -0
- package/dist/tools/report-tools.d.ts +5 -0
- package/dist/tools/report-tools.d.ts.map +1 -0
- package/dist/tools/report-tools.js +167 -0
- package/dist/tools/report-tools.js.map +1 -0
- package/dist/tools/review-diff-tools.d.ts +148 -0
- package/dist/tools/review-diff-tools.d.ts.map +1 -0
- package/dist/tools/review-diff-tools.js +852 -0
- package/dist/tools/review-diff-tools.js.map +1 -0
- package/dist/tools/route-tools.d.ts +32 -0
- package/dist/tools/route-tools.d.ts.map +1 -0
- package/dist/tools/route-tools.js +276 -0
- package/dist/tools/route-tools.js.map +1 -0
- package/dist/tools/search-ranker.d.ts +5 -0
- package/dist/tools/search-ranker.d.ts.map +1 -0
- package/dist/tools/search-ranker.js +142 -0
- package/dist/tools/search-ranker.js.map +1 -0
- package/dist/tools/search-tools.d.ts +24 -1
- package/dist/tools/search-tools.d.ts.map +1 -1
- package/dist/tools/search-tools.js +459 -225
- package/dist/tools/search-tools.js.map +1 -1
- package/dist/tools/secret-tools.d.ts +104 -0
- package/dist/tools/secret-tools.d.ts.map +1 -0
- package/dist/tools/secret-tools.js +410 -0
- package/dist/tools/secret-tools.js.map +1 -0
- package/dist/tools/symbol-tools.d.ts +90 -2
- package/dist/tools/symbol-tools.d.ts.map +1 -1
- package/dist/tools/symbol-tools.js +576 -42
- package/dist/tools/symbol-tools.js.map +1 -1
- package/dist/types.d.ts +34 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/framework-detect.d.ts +5 -0
- package/dist/utils/framework-detect.d.ts.map +1 -0
- package/dist/utils/framework-detect.js +36 -0
- package/dist/utils/framework-detect.js.map +1 -0
- package/dist/utils/glob.d.ts +19 -0
- package/dist/utils/glob.d.ts.map +1 -0
- package/dist/utils/glob.js +74 -0
- package/dist/utils/glob.js.map +1 -0
- package/dist/utils/import-graph.d.ts +29 -0
- package/dist/utils/import-graph.d.ts.map +1 -0
- package/dist/utils/import-graph.js +125 -0
- package/dist/utils/import-graph.js.map +1 -0
- package/dist/utils/test-file.d.ts.map +1 -1
- package/dist/utils/test-file.js +1 -0
- package/dist/utils/test-file.js.map +1 -1
- package/dist/utils/walk.d.ts +45 -0
- package/dist/utils/walk.d.ts.map +1 -0
- package/dist/utils/walk.js +87 -0
- package/dist/utils/walk.js.map +1 -0
- package/package.json +12 -5
- package/rules/codesift.md +187 -0
- package/rules/codesift.mdc +192 -0
- package/rules/codex.md +187 -0
- package/rules/gemini.md +187 -0
|
@@ -0,0 +1,1453 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** Boolean that also accepts "true"/"false" strings (LLMs often send strings instead of booleans) */
|
|
3
|
+
const zBool = () => z.union([z.boolean(), z.string().transform((s) => s === "true")]).optional();
|
|
4
|
+
import { wrapTool, registerShortener } from "./server-helpers.js";
|
|
5
|
+
import { indexFolder, indexFile, indexRepo, listAllRepos, invalidateCache } from "./tools/index-tools.js";
|
|
6
|
+
import { searchSymbols, searchText, semanticSearch } from "./tools/search-tools.js";
|
|
7
|
+
import { getFileTree, getFileOutline, getRepoOutline, suggestQueries } from "./tools/outline-tools.js";
|
|
8
|
+
import { getSymbol, getSymbols, findAndShow, findReferences, findReferencesBatch, findDeadCode, getContextBundle, formatRefsCompact, formatSymbolCompact, formatSymbolsCompact, formatBundleCompact } from "./tools/symbol-tools.js";
|
|
9
|
+
import { traceCallChain } from "./tools/graph-tools.js";
|
|
10
|
+
import { impactAnalysis } from "./tools/impact-tools.js";
|
|
11
|
+
import { traceRoute } from "./tools/route-tools.js";
|
|
12
|
+
import { detectCommunities } from "./tools/community-tools.js";
|
|
13
|
+
import { assembleContext, getKnowledgeMap } from "./tools/context-tools.js";
|
|
14
|
+
import { diffOutline, changedSymbols } from "./tools/diff-tools.js";
|
|
15
|
+
import { generateClaudeMd } from "./tools/generate-tools.js";
|
|
16
|
+
import { codebaseRetrieval } from "./retrieval/codebase-retrieval.js";
|
|
17
|
+
import { analyzeComplexity } from "./tools/complexity-tools.js";
|
|
18
|
+
import { findClones } from "./tools/clone-tools.js";
|
|
19
|
+
import { analyzeHotspots } from "./tools/hotspot-tools.js";
|
|
20
|
+
import { crossRepoSearchSymbols, crossRepoFindReferences } from "./tools/cross-repo-tools.js";
|
|
21
|
+
import { searchPatterns, listPatterns } from "./tools/pattern-tools.js";
|
|
22
|
+
import { generateReport } from "./tools/report-tools.js";
|
|
23
|
+
import { getUsageStats, formatUsageReport } from "./storage/usage-stats.js";
|
|
24
|
+
import { goToDefinition, getTypeInfo, renameSymbol, getCallHierarchy } from "./lsp/lsp-tools.js";
|
|
25
|
+
import { indexConversations, searchConversations, searchAllConversations, findConversationsForSymbol } from "./tools/conversation-tools.js";
|
|
26
|
+
import { scanSecrets } from "./tools/secret-tools.js";
|
|
27
|
+
import { consolidateMemories, readMemory } from "./tools/memory-tools.js";
|
|
28
|
+
import { createAnalysisPlan, writeScratchpad, readScratchpad, listScratchpad, updateStepStatus, getPlan, listPlans } from "./tools/coordinator-tools.js";
|
|
29
|
+
import { frequencyAnalysis } from "./tools/frequency-tools.js";
|
|
30
|
+
import { reviewDiff } from "./tools/review-diff-tools.js";
|
|
31
|
+
import { formatComplexityCompact, formatComplexityCounts, formatClonesCompact, formatClonesCounts, formatHotspotsCompact, formatHotspotsCounts } from "./formatters-shortening.js";
|
|
32
|
+
import { formatSearchSymbols, formatFileTree, formatFileOutline, formatSearchPatterns, formatDeadCode, formatComplexity, formatClones, formatHotspots, formatRepoOutline, formatSuggestQueries, formatSecrets, formatConversations, formatRoles, formatAssembleContext, formatCommunities, formatCallTree, formatTraceRoute, formatKnowledgeMap, formatImpactAnalysis, formatDiffOutline, formatChangedSymbols, formatReviewDiff } from "./formatters.js";
|
|
33
|
+
const zFiniteNumber = z.number().finite();
|
|
34
|
+
/** Coerce string→number for numeric params while rejecting NaN/empty strings. */
|
|
35
|
+
export const zNum = () => z.union([
|
|
36
|
+
zFiniteNumber,
|
|
37
|
+
z.string()
|
|
38
|
+
.trim()
|
|
39
|
+
.min(1, "Expected a number")
|
|
40
|
+
.transform((value) => Number(value))
|
|
41
|
+
.pipe(zFiniteNumber),
|
|
42
|
+
]).optional();
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Registered tool handles — populated by registerTools(), used by describe_tools reveal
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
const toolHandles = new Map();
|
|
47
|
+
/** Get a registered tool handle by name (for testing and describe_tools reveal) */
|
|
48
|
+
export function getToolHandle(name) {
|
|
49
|
+
return toolHandles.get(name);
|
|
50
|
+
}
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Output schemas — typed results for structured validation & documentation
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
export const OutputSchemas = {
|
|
55
|
+
/** search_symbols, cross_repo_search */
|
|
56
|
+
searchResults: z.string().describe("Formatted search results: file:line kind name signature"),
|
|
57
|
+
/** get_file_tree */
|
|
58
|
+
fileTree: z.string().describe("File tree with symbol counts per file"),
|
|
59
|
+
/** get_file_outline */
|
|
60
|
+
fileOutline: z.string().describe("Symbol outline: line:end_line kind name"),
|
|
61
|
+
/** get_symbol */
|
|
62
|
+
symbol: z.string().nullable().describe("Symbol source code or null if not found"),
|
|
63
|
+
/** find_references */
|
|
64
|
+
references: z.string().describe("References in file:line: context format"),
|
|
65
|
+
/** trace_call_chain */
|
|
66
|
+
callTree: z.string().describe("Call tree hierarchy or Mermaid diagram"),
|
|
67
|
+
/** impact_analysis */
|
|
68
|
+
impactAnalysis: z.string().describe("Changed files and affected symbols with risk levels"),
|
|
69
|
+
/** codebase_retrieval */
|
|
70
|
+
batchResults: z.string().describe("Concatenated sub-query result sections"),
|
|
71
|
+
/** discover_tools */
|
|
72
|
+
toolDiscovery: z.object({
|
|
73
|
+
query: z.string(),
|
|
74
|
+
matches: z.array(z.object({
|
|
75
|
+
name: z.string(),
|
|
76
|
+
category: z.string(),
|
|
77
|
+
description: z.string(),
|
|
78
|
+
is_core: z.boolean(),
|
|
79
|
+
})),
|
|
80
|
+
total_tools: z.number(),
|
|
81
|
+
categories: z.array(z.string()),
|
|
82
|
+
}),
|
|
83
|
+
/** get_call_hierarchy */
|
|
84
|
+
callHierarchy: z.string().describe("Call hierarchy: symbol with incoming and outgoing calls"),
|
|
85
|
+
/** analyze_complexity */
|
|
86
|
+
complexity: z.string().describe("Complexity report: CC nest lines file:line name"),
|
|
87
|
+
/** find_dead_code */
|
|
88
|
+
deadCode: z.string().describe("Unused exported symbols list"),
|
|
89
|
+
/** find_clones */
|
|
90
|
+
clones: z.string().describe("Code clone pairs with similarity scores"),
|
|
91
|
+
/** scan_secrets */
|
|
92
|
+
secrets: z.string().describe("Secret findings with severity, type, and masked values"),
|
|
93
|
+
/** go_to_definition */
|
|
94
|
+
definition: z.string().nullable().describe("file:line (via lsp|index) with preview"),
|
|
95
|
+
/** get_type_info */
|
|
96
|
+
typeInfo: z.union([
|
|
97
|
+
z.object({ type: z.string(), documentation: z.string().optional(), via: z.literal("lsp") }),
|
|
98
|
+
z.object({ via: z.literal("unavailable"), hint: z.string() }),
|
|
99
|
+
]),
|
|
100
|
+
/** rename_symbol */
|
|
101
|
+
renameResult: z.object({
|
|
102
|
+
files_changed: z.number(),
|
|
103
|
+
edits: z.array(z.object({ file: z.string(), changes: z.number() })),
|
|
104
|
+
}),
|
|
105
|
+
/** usage_stats */
|
|
106
|
+
usageStats: z.object({ report: z.string() }),
|
|
107
|
+
/** list_repos */
|
|
108
|
+
repoList: z.union([z.array(z.string()), z.array(z.object({ name: z.string() }).passthrough())]),
|
|
109
|
+
};
|
|
110
|
+
/** Tools always registered with full schema — top 10 by usage (91% of calls) + essentials */
|
|
111
|
+
const CORE_TOOL_NAMES = new Set([
|
|
112
|
+
"search_text", // #1: 1536 calls, 36%
|
|
113
|
+
"codebase_retrieval", // #2: 510 calls, 12%
|
|
114
|
+
"get_file_outline", // #3: 342 calls, 8%
|
|
115
|
+
"search_symbols", // #4: 321 calls, 8%
|
|
116
|
+
"list_repos", // #5: 223 calls, 5%
|
|
117
|
+
"get_file_tree", // #6: 218 calls, 5%
|
|
118
|
+
"index_file", // #7: 163 calls, 4% — lightweight schema
|
|
119
|
+
"get_symbol", // #8: 135 calls, 3%
|
|
120
|
+
"index_conversations", // #9: 125 calls, 3% — lightweight schema
|
|
121
|
+
"search_patterns", // #10: 122 calls, 3%
|
|
122
|
+
"index_folder", // essential: repo onboarding
|
|
123
|
+
"discover_tools", // meta: discovers deferred tools
|
|
124
|
+
]);
|
|
125
|
+
/** Get all tool definitions (exported for testing) */
|
|
126
|
+
export function getToolDefinitions() {
|
|
127
|
+
return TOOL_DEFINITIONS;
|
|
128
|
+
}
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// Tool definitions — data-driven registration (CQ14: eliminates 30× boilerplate)
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
const TOOL_DEFINITIONS = [
|
|
133
|
+
// --- Indexing ---
|
|
134
|
+
{
|
|
135
|
+
name: "index_folder",
|
|
136
|
+
category: "indexing",
|
|
137
|
+
searchHint: "index local folder directory project parse symbols",
|
|
138
|
+
description: "Index a local folder, extracting symbols and building the search index",
|
|
139
|
+
schema: {
|
|
140
|
+
path: z.string().describe("Absolute path to the folder to index"),
|
|
141
|
+
incremental: zBool().describe("Only re-index changed files"),
|
|
142
|
+
include_paths: z.union([z.array(z.string()), z.string().transform((s) => JSON.parse(s))]).optional().describe("Glob patterns to include. Can be passed as JSON string."),
|
|
143
|
+
},
|
|
144
|
+
handler: (args) => indexFolder(args.path, {
|
|
145
|
+
incremental: args.incremental,
|
|
146
|
+
include_paths: args.include_paths,
|
|
147
|
+
}),
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: "index_repo",
|
|
151
|
+
category: "indexing",
|
|
152
|
+
searchHint: "clone remote git repository index",
|
|
153
|
+
description: "Clone and index a remote git repository",
|
|
154
|
+
schema: {
|
|
155
|
+
url: z.string().describe("Git clone URL"),
|
|
156
|
+
branch: z.string().optional().describe("Branch to checkout"),
|
|
157
|
+
include_paths: z.union([z.array(z.string()), z.string().transform((s) => JSON.parse(s))]).optional().describe("Glob patterns to include. Can be passed as JSON string."),
|
|
158
|
+
},
|
|
159
|
+
handler: (args) => indexRepo(args.url, {
|
|
160
|
+
branch: args.branch,
|
|
161
|
+
include_paths: args.include_paths,
|
|
162
|
+
}),
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: "list_repos",
|
|
166
|
+
category: "indexing",
|
|
167
|
+
searchHint: "list indexed repositories repos available",
|
|
168
|
+
outputSchema: OutputSchemas.repoList,
|
|
169
|
+
description: "List indexed repos. Set compact=false for full metadata. Cached per session.",
|
|
170
|
+
schema: {
|
|
171
|
+
compact: zBool().describe("true=names only (default), false=full metadata"),
|
|
172
|
+
},
|
|
173
|
+
handler: (args) => listAllRepos({ compact: args.compact ?? true }),
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: "invalidate_cache",
|
|
177
|
+
category: "indexing",
|
|
178
|
+
searchHint: "clear cache invalidate re-index refresh",
|
|
179
|
+
description: "Clear the index cache for a repository, forcing full re-index on next use",
|
|
180
|
+
schema: {
|
|
181
|
+
repo: z.string().describe("Repository identifier (e.g. local/my-project)"),
|
|
182
|
+
},
|
|
183
|
+
handler: (args) => invalidateCache(args.repo),
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: "index_file",
|
|
187
|
+
category: "indexing",
|
|
188
|
+
searchHint: "re-index single file update incremental",
|
|
189
|
+
description: "Re-index a single file after editing. Auto-finds repo, skips if unchanged.",
|
|
190
|
+
schema: {
|
|
191
|
+
path: z.string().describe("Absolute path to the file to re-index"),
|
|
192
|
+
},
|
|
193
|
+
handler: (args) => indexFile(args.path),
|
|
194
|
+
},
|
|
195
|
+
// --- Search ---
|
|
196
|
+
{
|
|
197
|
+
name: "search_symbols",
|
|
198
|
+
category: "search",
|
|
199
|
+
searchHint: "search find symbols functions classes types methods by name signature",
|
|
200
|
+
outputSchema: OutputSchemas.searchResults,
|
|
201
|
+
description: "Search symbols by name/signature. detail_level: compact (~15 tok), standard (default), full.",
|
|
202
|
+
schema: {
|
|
203
|
+
repo: z.string().describe("Repository identifier"),
|
|
204
|
+
query: z.string().describe("Search query string"),
|
|
205
|
+
kind: z.string().optional().describe("Filter by symbol kind (function, class, etc.)"),
|
|
206
|
+
file_pattern: z.string().optional().describe("Glob pattern to filter files"),
|
|
207
|
+
include_source: zBool().describe("Include full source code of each symbol"),
|
|
208
|
+
top_k: zNum().describe("Maximum number of results to return (default 50)"),
|
|
209
|
+
source_chars: zNum().describe("Truncate each symbol's source to N characters (reduces output size)"),
|
|
210
|
+
detail_level: z.enum(["compact", "standard", "full"]).optional().describe("compact (~15 tok), standard (default), full (all source)"),
|
|
211
|
+
token_budget: zNum().describe("Max tokens for results — greedily packs results until budget exhausted. Overrides top_k."),
|
|
212
|
+
rerank: zBool().describe("Rerank results using cross-encoder model for improved relevance (requires @huggingface/transformers)"),
|
|
213
|
+
},
|
|
214
|
+
handler: async (args) => {
|
|
215
|
+
const results = await searchSymbols(args.repo, args.query, {
|
|
216
|
+
kind: args.kind,
|
|
217
|
+
file_pattern: args.file_pattern,
|
|
218
|
+
include_source: args.include_source,
|
|
219
|
+
top_k: args.top_k,
|
|
220
|
+
source_chars: args.source_chars,
|
|
221
|
+
detail_level: args.detail_level,
|
|
222
|
+
token_budget: args.token_budget,
|
|
223
|
+
rerank: args.rerank,
|
|
224
|
+
});
|
|
225
|
+
return formatSearchSymbols(results);
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
name: "ast_query",
|
|
230
|
+
category: "search",
|
|
231
|
+
searchHint: "AST tree-sitter query structural pattern matching code shape",
|
|
232
|
+
description: "Search AST patterns via tree-sitter S-expressions. Finds code by structural shape.",
|
|
233
|
+
schema: {
|
|
234
|
+
repo: z.string().describe("Repository identifier"),
|
|
235
|
+
query: z.string().describe("Tree-sitter query in S-expression syntax"),
|
|
236
|
+
language: z.string().describe("Tree-sitter grammar: typescript, javascript, python, go, rust, java, ruby, php"),
|
|
237
|
+
file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
|
|
238
|
+
max_matches: zNum().describe("Maximum matches to return (default: 50)"),
|
|
239
|
+
},
|
|
240
|
+
handler: async (args) => {
|
|
241
|
+
const { astQuery } = await import("./tools/ast-query-tools.js");
|
|
242
|
+
return astQuery(args.repo, args.query, {
|
|
243
|
+
language: args.language,
|
|
244
|
+
file_pattern: args.file_pattern,
|
|
245
|
+
max_matches: args.max_matches,
|
|
246
|
+
});
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: "semantic_search",
|
|
251
|
+
category: "search",
|
|
252
|
+
searchHint: "semantic meaning intent concept embedding vector natural language",
|
|
253
|
+
description: "Search code by meaning using embeddings. For intent-based queries: 'error handling', 'auth flow'. Requires indexed embeddings.",
|
|
254
|
+
schema: {
|
|
255
|
+
repo: z.string().describe("Repository identifier"),
|
|
256
|
+
query: z.string().describe("Natural language query describing what you're looking for"),
|
|
257
|
+
top_k: zNum().describe("Number of results (default: 10)"),
|
|
258
|
+
file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
|
|
259
|
+
exclude_tests: zBool().describe("Exclude test files from results"),
|
|
260
|
+
rerank: zBool().describe("Re-rank results with cross-encoder for better precision"),
|
|
261
|
+
},
|
|
262
|
+
handler: async (args) => {
|
|
263
|
+
const opts = {};
|
|
264
|
+
if (args.top_k != null)
|
|
265
|
+
opts.top_k = args.top_k;
|
|
266
|
+
if (args.file_pattern != null)
|
|
267
|
+
opts.file_pattern = args.file_pattern;
|
|
268
|
+
if (args.exclude_tests != null)
|
|
269
|
+
opts.exclude_tests = args.exclude_tests;
|
|
270
|
+
if (args.rerank != null)
|
|
271
|
+
opts.rerank = args.rerank;
|
|
272
|
+
return semanticSearch(args.repo, args.query, opts);
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: "search_text",
|
|
277
|
+
category: "search",
|
|
278
|
+
searchHint: "full-text search grep regex keyword content files",
|
|
279
|
+
description: "Full-text search across all files. For conceptual queries use semantic_search.",
|
|
280
|
+
schema: {
|
|
281
|
+
repo: z.string().describe("Repository identifier"),
|
|
282
|
+
query: z.string().describe("Search query or regex pattern"),
|
|
283
|
+
regex: zBool().describe("Treat query as a regex pattern"),
|
|
284
|
+
context_lines: zNum().describe("Number of context lines around each match"),
|
|
285
|
+
file_pattern: z.string().optional().describe("Glob pattern to filter files"),
|
|
286
|
+
max_results: zNum().describe("Maximum number of matching lines to return (default 200)"),
|
|
287
|
+
group_by_file: zBool().describe("Group by file: {file, count, lines[], first_match}. ~80% less output."),
|
|
288
|
+
auto_group: zBool().describe("Auto group_by_file when >50 matches."),
|
|
289
|
+
ranked: z.boolean().optional().describe("Classify hits by containing symbol and rank by centrality"),
|
|
290
|
+
},
|
|
291
|
+
handler: (args) => searchText(args.repo, args.query, {
|
|
292
|
+
regex: args.regex,
|
|
293
|
+
context_lines: args.context_lines,
|
|
294
|
+
file_pattern: args.file_pattern,
|
|
295
|
+
max_results: args.max_results,
|
|
296
|
+
group_by_file: args.group_by_file,
|
|
297
|
+
auto_group: args.auto_group,
|
|
298
|
+
ranked: args.ranked,
|
|
299
|
+
}),
|
|
300
|
+
},
|
|
301
|
+
// --- Outline ---
|
|
302
|
+
{
|
|
303
|
+
name: "get_file_tree",
|
|
304
|
+
category: "outline",
|
|
305
|
+
searchHint: "file tree directory structure listing files symbols",
|
|
306
|
+
outputSchema: OutputSchemas.fileTree,
|
|
307
|
+
description: "File tree with symbol counts. compact=true for flat list (10-50x less output). Cached 5min.",
|
|
308
|
+
schema: {
|
|
309
|
+
repo: z.string().describe("Repository identifier"),
|
|
310
|
+
path_prefix: z.string().optional().describe("Filter to a subtree by path prefix"),
|
|
311
|
+
name_pattern: z.string().optional().describe("Glob pattern to filter file names"),
|
|
312
|
+
depth: zNum().describe("Maximum directory depth to traverse"),
|
|
313
|
+
compact: zBool().describe("Return flat list of {path, symbols} instead of nested tree (much less output)"),
|
|
314
|
+
min_symbols: zNum().describe("Only include files with at least this many symbols"),
|
|
315
|
+
},
|
|
316
|
+
handler: async (args) => {
|
|
317
|
+
const result = await getFileTree(args.repo, {
|
|
318
|
+
path_prefix: args.path_prefix,
|
|
319
|
+
name_pattern: args.name_pattern,
|
|
320
|
+
depth: args.depth,
|
|
321
|
+
compact: args.compact,
|
|
322
|
+
min_symbols: args.min_symbols,
|
|
323
|
+
});
|
|
324
|
+
return formatFileTree(result);
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
name: "get_file_outline",
|
|
329
|
+
category: "outline",
|
|
330
|
+
searchHint: "file outline symbols functions classes exports single file",
|
|
331
|
+
outputSchema: OutputSchemas.fileOutline,
|
|
332
|
+
description: "Get the symbol outline of a single file (functions, classes, exports)",
|
|
333
|
+
schema: {
|
|
334
|
+
repo: z.string().describe("Repository identifier"),
|
|
335
|
+
file_path: z.string().describe("Relative file path within the repository"),
|
|
336
|
+
},
|
|
337
|
+
handler: async (args) => {
|
|
338
|
+
const result = await getFileOutline(args.repo, args.file_path);
|
|
339
|
+
return formatFileOutline(result);
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
name: "get_repo_outline",
|
|
344
|
+
category: "outline",
|
|
345
|
+
searchHint: "repository outline overview directory structure high-level",
|
|
346
|
+
description: "Get a high-level outline of the entire repository grouped by directory",
|
|
347
|
+
schema: {
|
|
348
|
+
repo: z.string().describe("Repository identifier"),
|
|
349
|
+
},
|
|
350
|
+
handler: async (args) => {
|
|
351
|
+
const result = await getRepoOutline(args.repo);
|
|
352
|
+
return formatRepoOutline(result);
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
name: "suggest_queries",
|
|
357
|
+
category: "outline",
|
|
358
|
+
searchHint: "suggest queries explore unfamiliar repo onboarding first call",
|
|
359
|
+
description: "Suggest queries for exploring a new repo. Returns top files, kind distribution, examples.",
|
|
360
|
+
schema: {
|
|
361
|
+
repo: z.string().describe("Repository identifier"),
|
|
362
|
+
},
|
|
363
|
+
handler: async (args) => {
|
|
364
|
+
const result = await suggestQueries(args.repo);
|
|
365
|
+
return formatSuggestQueries(result);
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
// --- Symbol retrieval ---
|
|
369
|
+
{
|
|
370
|
+
name: "get_symbol",
|
|
371
|
+
category: "symbols",
|
|
372
|
+
searchHint: "get retrieve single symbol source code by ID",
|
|
373
|
+
outputSchema: OutputSchemas.symbol,
|
|
374
|
+
description: "Get symbol by ID with source. Auto-prefetches children for classes. For batch: get_symbols. For context: get_context_bundle.",
|
|
375
|
+
schema: {
|
|
376
|
+
repo: z.string().describe("Repository identifier"),
|
|
377
|
+
symbol_id: z.string().describe("Unique symbol identifier"),
|
|
378
|
+
include_related: zBool().describe("Include children/related symbols (default: true)"),
|
|
379
|
+
},
|
|
380
|
+
handler: async (args) => {
|
|
381
|
+
const opts = {};
|
|
382
|
+
if (args.include_related != null)
|
|
383
|
+
opts.include_related = args.include_related;
|
|
384
|
+
const result = await getSymbol(args.repo, args.symbol_id, opts);
|
|
385
|
+
if (!result)
|
|
386
|
+
return null;
|
|
387
|
+
let text = formatSymbolCompact(result.symbol);
|
|
388
|
+
if (result.related && result.related.length > 0) {
|
|
389
|
+
text += "\n\n--- children ---\n" + result.related.map((s) => `${s.kind} ${s.name}${s.signature ? s.signature : ""} [${s.file}:${s.start_line}]`).join("\n");
|
|
390
|
+
}
|
|
391
|
+
return text;
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
name: "get_symbols",
|
|
396
|
+
category: "symbols",
|
|
397
|
+
searchHint: "batch get multiple symbols by IDs",
|
|
398
|
+
description: "Retrieve multiple symbols by ID in a single batch call",
|
|
399
|
+
schema: {
|
|
400
|
+
repo: z.string().describe("Repository identifier"),
|
|
401
|
+
symbol_ids: z.union([
|
|
402
|
+
z.array(z.string()),
|
|
403
|
+
z.string().transform((s) => JSON.parse(s)),
|
|
404
|
+
]).describe("Array of symbol identifiers. Can be passed as JSON string."),
|
|
405
|
+
},
|
|
406
|
+
handler: async (args) => {
|
|
407
|
+
const syms = await getSymbols(args.repo, args.symbol_ids);
|
|
408
|
+
return formatSymbolsCompact(syms);
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
name: "find_and_show",
|
|
413
|
+
category: "symbols",
|
|
414
|
+
searchHint: "find symbol by name show source code references",
|
|
415
|
+
description: "Find a symbol by name and show its source, optionally including references",
|
|
416
|
+
schema: {
|
|
417
|
+
repo: z.string().describe("Repository identifier"),
|
|
418
|
+
query: z.string().describe("Symbol name or query to search for"),
|
|
419
|
+
include_refs: zBool().describe("Include locations that reference this symbol"),
|
|
420
|
+
},
|
|
421
|
+
handler: async (args) => {
|
|
422
|
+
const result = await findAndShow(args.repo, args.query, args.include_refs);
|
|
423
|
+
if (!result)
|
|
424
|
+
return null;
|
|
425
|
+
let text = formatSymbolCompact(result.symbol);
|
|
426
|
+
if (result.references) {
|
|
427
|
+
text += `\n\n--- references ---\n${formatRefsCompact(result.references)}`;
|
|
428
|
+
}
|
|
429
|
+
return text;
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
name: "get_context_bundle",
|
|
434
|
+
category: "symbols",
|
|
435
|
+
searchHint: "context bundle symbol imports siblings callers one call",
|
|
436
|
+
description: "Symbol + imports + siblings in one call. Saves 2-3 round-trips.",
|
|
437
|
+
schema: {
|
|
438
|
+
repo: z.string().describe("Repository identifier"),
|
|
439
|
+
symbol_name: z.string().describe("Symbol name to find"),
|
|
440
|
+
},
|
|
441
|
+
handler: async (args) => {
|
|
442
|
+
const bundle = await getContextBundle(args.repo, args.symbol_name);
|
|
443
|
+
if (!bundle)
|
|
444
|
+
return null;
|
|
445
|
+
return formatBundleCompact(bundle);
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
// --- References & call graph ---
|
|
449
|
+
{
|
|
450
|
+
name: "find_references",
|
|
451
|
+
category: "graph",
|
|
452
|
+
searchHint: "find references usages callers who uses symbol",
|
|
453
|
+
outputSchema: OutputSchemas.references,
|
|
454
|
+
description: "Find all references to a symbol. Pass symbol_names array for batch search.",
|
|
455
|
+
schema: {
|
|
456
|
+
repo: z.string().describe("Repository identifier"),
|
|
457
|
+
symbol_name: z.string().optional().describe("Name of the symbol to find references for"),
|
|
458
|
+
symbol_names: z.union([z.array(z.string()), z.string().transform((s) => JSON.parse(s))]).optional()
|
|
459
|
+
.describe("Array of symbol names for batch search (reads each file once). Can be JSON string."),
|
|
460
|
+
file_pattern: z.string().optional().describe("Glob pattern to filter files"),
|
|
461
|
+
},
|
|
462
|
+
handler: async (args) => {
|
|
463
|
+
const names = args.symbol_names;
|
|
464
|
+
if (names && names.length > 0) {
|
|
465
|
+
return findReferencesBatch(args.repo, names, args.file_pattern);
|
|
466
|
+
}
|
|
467
|
+
const refs = await findReferences(args.repo, args.symbol_name, args.file_pattern);
|
|
468
|
+
// Compact format: drop col, use file:line: context (matches grep output)
|
|
469
|
+
return formatRefsCompact(refs);
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
name: "trace_call_chain",
|
|
474
|
+
category: "graph",
|
|
475
|
+
searchHint: "trace call chain callers callees dependency graph mermaid",
|
|
476
|
+
outputSchema: OutputSchemas.callTree,
|
|
477
|
+
description: "Trace call chain: callers or callees. output_format='mermaid' for diagram.",
|
|
478
|
+
schema: {
|
|
479
|
+
repo: z.string().describe("Repository identifier"),
|
|
480
|
+
symbol_name: z.string().describe("Name of the symbol to trace"),
|
|
481
|
+
direction: z.enum(["callers", "callees"]).describe("Trace direction"),
|
|
482
|
+
depth: zNum().describe("Maximum depth to traverse the call graph (default: 1)"),
|
|
483
|
+
include_source: zBool().describe("Include full source code of each symbol (default: false)"),
|
|
484
|
+
include_tests: zBool().describe("Include test files in trace results (default: false)"),
|
|
485
|
+
output_format: z.enum(["json", "mermaid"]).optional().describe("Output format: 'json' (default) or 'mermaid' (flowchart diagram)"),
|
|
486
|
+
},
|
|
487
|
+
handler: async (args) => {
|
|
488
|
+
const result = await traceCallChain(args.repo, args.symbol_name, args.direction, {
|
|
489
|
+
depth: args.depth,
|
|
490
|
+
include_source: args.include_source,
|
|
491
|
+
include_tests: args.include_tests,
|
|
492
|
+
output_format: args.output_format,
|
|
493
|
+
});
|
|
494
|
+
return formatCallTree(result);
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
name: "impact_analysis",
|
|
499
|
+
category: "graph",
|
|
500
|
+
searchHint: "impact analysis blast radius git changes affected symbols",
|
|
501
|
+
outputSchema: OutputSchemas.impactAnalysis,
|
|
502
|
+
description: "Blast radius of git changes — affected symbols and files.",
|
|
503
|
+
schema: {
|
|
504
|
+
repo: z.string().describe("Repository identifier"),
|
|
505
|
+
since: z.string().describe("Git ref to compare from (e.g. HEAD~3, commit SHA, branch)"),
|
|
506
|
+
depth: zNum().describe("Depth of dependency traversal"),
|
|
507
|
+
until: z.string().optional().describe("Git ref to compare to (defaults to HEAD)"),
|
|
508
|
+
include_source: zBool().describe("Include full source code of affected symbols (default: false)"),
|
|
509
|
+
},
|
|
510
|
+
handler: async (args) => {
|
|
511
|
+
const result = await impactAnalysis(args.repo, args.since, {
|
|
512
|
+
depth: args.depth,
|
|
513
|
+
until: args.until,
|
|
514
|
+
include_source: args.include_source,
|
|
515
|
+
});
|
|
516
|
+
return formatImpactAnalysis(result);
|
|
517
|
+
},
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
name: "trace_route",
|
|
521
|
+
category: "graph",
|
|
522
|
+
searchHint: "trace HTTP route handler API endpoint service database NestJS Express Next.js",
|
|
523
|
+
description: "Trace HTTP route → handler → service → DB. NestJS, Next.js, Express.",
|
|
524
|
+
schema: {
|
|
525
|
+
repo: z.string().describe("Repository identifier"),
|
|
526
|
+
path: z.string().describe("URL path to trace (e.g. '/api/users', '/api/projects/:id')"),
|
|
527
|
+
output_format: z.enum(["json", "mermaid"]).optional().describe("Output format: 'json' (default) or 'mermaid' (sequence diagram)"),
|
|
528
|
+
},
|
|
529
|
+
handler: async (args) => {
|
|
530
|
+
const result = await traceRoute(args.repo, args.path, args.output_format);
|
|
531
|
+
return formatTraceRoute(result);
|
|
532
|
+
},
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
name: "go_to_definition",
|
|
536
|
+
category: "lsp",
|
|
537
|
+
searchHint: "go to definition jump navigate LSP language server",
|
|
538
|
+
outputSchema: OutputSchemas.definition,
|
|
539
|
+
description: "Go to the definition of a symbol. Uses LSP when available for type-safe precision, falls back to index search.",
|
|
540
|
+
schema: {
|
|
541
|
+
repo: z.string().describe("Repository identifier"),
|
|
542
|
+
symbol_name: z.string().describe("Symbol name to find definition of"),
|
|
543
|
+
file_path: z.string().optional().describe("File containing the symbol reference (for LSP precision)"),
|
|
544
|
+
line: zNum().describe("0-based line number of the reference"),
|
|
545
|
+
character: zNum().describe("0-based column of the reference"),
|
|
546
|
+
},
|
|
547
|
+
handler: async (args) => {
|
|
548
|
+
const result = await goToDefinition(args.repo, args.symbol_name, args.file_path, args.line, args.character);
|
|
549
|
+
if (!result)
|
|
550
|
+
return null;
|
|
551
|
+
const preview = result.preview ? `\n${result.preview}` : "";
|
|
552
|
+
return `${result.file}:${result.line + 1} (via ${result.via})${preview}`;
|
|
553
|
+
},
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
name: "get_type_info",
|
|
557
|
+
category: "lsp",
|
|
558
|
+
searchHint: "type information hover documentation return type parameters LSP",
|
|
559
|
+
outputSchema: OutputSchemas.typeInfo,
|
|
560
|
+
description: "Get type info via LSP hover (return type, params, docs). Hint if LSP unavailable.",
|
|
561
|
+
schema: {
|
|
562
|
+
repo: z.string().describe("Repository identifier"),
|
|
563
|
+
symbol_name: z.string().describe("Symbol name to get type info for"),
|
|
564
|
+
file_path: z.string().optional().describe("File containing the symbol"),
|
|
565
|
+
line: zNum().describe("0-based line number"),
|
|
566
|
+
character: zNum().describe("0-based column"),
|
|
567
|
+
},
|
|
568
|
+
handler: (args) => getTypeInfo(args.repo, args.symbol_name, args.file_path, args.line, args.character),
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
name: "rename_symbol",
|
|
572
|
+
category: "lsp",
|
|
573
|
+
searchHint: "rename symbol refactor LSP type-safe all files",
|
|
574
|
+
outputSchema: OutputSchemas.renameResult,
|
|
575
|
+
description: "Rename symbol across all files via LSP. Type-safe, updates imports/refs.",
|
|
576
|
+
schema: {
|
|
577
|
+
repo: z.string().describe("Repository identifier"),
|
|
578
|
+
symbol_name: z.string().describe("Current name of the symbol to rename"),
|
|
579
|
+
new_name: z.string().describe("New name for the symbol"),
|
|
580
|
+
file_path: z.string().optional().describe("File containing the symbol"),
|
|
581
|
+
line: zNum().describe("0-based line number"),
|
|
582
|
+
character: zNum().describe("0-based column"),
|
|
583
|
+
},
|
|
584
|
+
handler: (args) => renameSymbol(args.repo, args.symbol_name, args.new_name, args.file_path, args.line, args.character),
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
name: "get_call_hierarchy",
|
|
588
|
+
category: "lsp",
|
|
589
|
+
searchHint: "call hierarchy incoming outgoing calls who calls what calls LSP callers callees",
|
|
590
|
+
outputSchema: OutputSchemas.callHierarchy,
|
|
591
|
+
description: "LSP call hierarchy: incoming + outgoing calls. Complements trace_call_chain.",
|
|
592
|
+
schema: {
|
|
593
|
+
repo: z.string().describe("Repository identifier"),
|
|
594
|
+
symbol_name: z.string().describe("Symbol name to get call hierarchy for"),
|
|
595
|
+
file_path: z.string().optional().describe("File containing the symbol (for LSP precision)"),
|
|
596
|
+
line: zNum().describe("0-based line number"),
|
|
597
|
+
character: zNum().describe("0-based column"),
|
|
598
|
+
},
|
|
599
|
+
handler: async (args) => {
|
|
600
|
+
const result = await getCallHierarchy(args.repo, args.symbol_name, args.file_path, args.line, args.character);
|
|
601
|
+
if (result.via === "unavailable") {
|
|
602
|
+
return { ...result };
|
|
603
|
+
}
|
|
604
|
+
// Compact text format
|
|
605
|
+
const lines = [];
|
|
606
|
+
lines.push(`${result.symbol.kind} ${result.symbol.name} (${result.symbol.file}:${result.symbol.line})`);
|
|
607
|
+
if (result.incoming.length > 0) {
|
|
608
|
+
lines.push(`\n--- incoming calls (${result.incoming.length}) ---`);
|
|
609
|
+
for (const c of result.incoming) {
|
|
610
|
+
lines.push(` ${c.kind} ${c.name} (${c.file}:${c.line})`);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
if (result.outgoing.length > 0) {
|
|
614
|
+
lines.push(`\n--- outgoing calls (${result.outgoing.length}) ---`);
|
|
615
|
+
for (const c of result.outgoing) {
|
|
616
|
+
lines.push(` ${c.kind} ${c.name} (${c.file}:${c.line})`);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
if (result.incoming.length === 0 && result.outgoing.length === 0) {
|
|
620
|
+
lines.push("\nNo incoming or outgoing calls found.");
|
|
621
|
+
}
|
|
622
|
+
return lines.join("\n");
|
|
623
|
+
},
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
name: "detect_communities",
|
|
627
|
+
category: "architecture",
|
|
628
|
+
searchHint: "community detection clusters modules Louvain import graph boundaries",
|
|
629
|
+
description: "Louvain community detection on import graph. Discovers module boundaries.",
|
|
630
|
+
schema: {
|
|
631
|
+
repo: z.string().describe("Repository identifier"),
|
|
632
|
+
focus: z.string().optional().describe("Path substring to filter files (e.g. 'src/lib')"),
|
|
633
|
+
resolution: zNum().describe("Louvain resolution: higher = more smaller communities, lower = fewer larger (default: 1.0)"),
|
|
634
|
+
output_format: z.enum(["json", "mermaid"]).optional().describe("Output format: 'json' (default) or 'mermaid' (graph diagram)"),
|
|
635
|
+
},
|
|
636
|
+
handler: async (args) => {
|
|
637
|
+
const result = await detectCommunities(args.repo, args.focus, args.resolution, args.output_format);
|
|
638
|
+
return formatCommunities(result);
|
|
639
|
+
},
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
name: "find_circular_deps",
|
|
643
|
+
category: "architecture",
|
|
644
|
+
searchHint: "circular dependency cycle import loop detection",
|
|
645
|
+
description: "Detect circular dependencies in the import graph via DFS. Returns file-level cycles.",
|
|
646
|
+
schema: {
|
|
647
|
+
repo: z.string().describe("Repository identifier"),
|
|
648
|
+
file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
|
|
649
|
+
max_cycles: zNum().describe("Maximum cycles to report (default: 50)"),
|
|
650
|
+
},
|
|
651
|
+
handler: async (args) => {
|
|
652
|
+
const { findCircularDeps } = await import("./tools/graph-tools.js");
|
|
653
|
+
const opts = {};
|
|
654
|
+
if (args.file_pattern != null)
|
|
655
|
+
opts.file_pattern = args.file_pattern;
|
|
656
|
+
if (args.max_cycles != null)
|
|
657
|
+
opts.max_cycles = args.max_cycles;
|
|
658
|
+
const result = await findCircularDeps(args.repo, opts);
|
|
659
|
+
if (result.cycles.length === 0) {
|
|
660
|
+
return `No circular dependencies found (scanned ${result.total_files} files, ${result.total_edges} edges)`;
|
|
661
|
+
}
|
|
662
|
+
const lines = [`${result.cycles.length} circular dependencies found (${result.total_files} files, ${result.total_edges} edges):\n`];
|
|
663
|
+
for (const c of result.cycles) {
|
|
664
|
+
lines.push(` ${c.cycle.join(" → ")}`);
|
|
665
|
+
}
|
|
666
|
+
return lines.join("\n");
|
|
667
|
+
},
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
name: "check_boundaries",
|
|
671
|
+
category: "architecture",
|
|
672
|
+
searchHint: "boundary rules architecture enforcement imports CI gate hexagonal onion",
|
|
673
|
+
description: "Check architecture boundary rules against imports. Path substring matching.",
|
|
674
|
+
schema: {
|
|
675
|
+
repo: z.string().describe("Repository identifier"),
|
|
676
|
+
rules: z.array(z.object({
|
|
677
|
+
from: z.string().describe("Path substring matching source files (e.g. 'src/domain')"),
|
|
678
|
+
cannot_import: z.array(z.string()).optional().describe("Path patterns that matched files must NOT import"),
|
|
679
|
+
can_only_import: z.array(z.string()).optional().describe("Path patterns that matched files may ONLY import (allowlist)"),
|
|
680
|
+
})).describe("Array of boundary rules to check"),
|
|
681
|
+
file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
|
|
682
|
+
},
|
|
683
|
+
handler: async (args) => {
|
|
684
|
+
const { checkBoundaries } = await import("./tools/boundary-tools.js");
|
|
685
|
+
return checkBoundaries(args.repo, args.rules, { file_pattern: args.file_pattern });
|
|
686
|
+
},
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
name: "classify_roles",
|
|
690
|
+
category: "architecture",
|
|
691
|
+
searchHint: "classify roles entry core utility dead leaf symbol architecture",
|
|
692
|
+
description: "Classify symbol roles (entry/core/utility/dead/leaf) by call graph connectivity.",
|
|
693
|
+
schema: {
|
|
694
|
+
repo: z.string().describe("Repository identifier"),
|
|
695
|
+
file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
|
|
696
|
+
include_tests: zBool().describe("Include test files (default: false)"),
|
|
697
|
+
top_n: zNum().describe("Maximum number of symbols to return (default: 100)"),
|
|
698
|
+
},
|
|
699
|
+
handler: async (args) => {
|
|
700
|
+
const { classifySymbolRoles } = await import("./tools/graph-tools.js");
|
|
701
|
+
const result = await classifySymbolRoles(args.repo, {
|
|
702
|
+
file_pattern: args.file_pattern,
|
|
703
|
+
include_tests: args.include_tests,
|
|
704
|
+
top_n: args.top_n,
|
|
705
|
+
});
|
|
706
|
+
return formatRoles(result);
|
|
707
|
+
},
|
|
708
|
+
},
|
|
709
|
+
// --- Context & knowledge ---
|
|
710
|
+
{
|
|
711
|
+
name: "assemble_context",
|
|
712
|
+
category: "context",
|
|
713
|
+
searchHint: "assemble context token budget L0 L1 L2 L3 source signatures summaries",
|
|
714
|
+
description: "Assemble code context within token budget. L0=source, L1=signatures, L2=files, L3=dirs.",
|
|
715
|
+
schema: {
|
|
716
|
+
repo: z.string().describe("Repository identifier"),
|
|
717
|
+
query: z.string().describe("Natural language query describing what context is needed"),
|
|
718
|
+
token_budget: zNum().describe("Maximum tokens for the assembled context"),
|
|
719
|
+
level: z.enum(["L0", "L1", "L2", "L3"]).optional().describe("L0=source (default), L1=signatures, L2=files, L3=dirs"),
|
|
720
|
+
rerank: zBool().describe("Rerank results using cross-encoder model for improved relevance (requires @huggingface/transformers)"),
|
|
721
|
+
},
|
|
722
|
+
handler: async (args) => {
|
|
723
|
+
const result = await assembleContext(args.repo, args.query, args.token_budget, args.level, args.rerank);
|
|
724
|
+
return formatAssembleContext(result);
|
|
725
|
+
},
|
|
726
|
+
},
|
|
727
|
+
{
|
|
728
|
+
name: "get_knowledge_map",
|
|
729
|
+
category: "context",
|
|
730
|
+
searchHint: "knowledge map module dependency graph architecture overview mermaid",
|
|
731
|
+
description: "Get the module dependency map showing how files and directories relate",
|
|
732
|
+
schema: {
|
|
733
|
+
repo: z.string().describe("Repository identifier"),
|
|
734
|
+
focus: z.string().optional().describe("Focus on a specific module or directory"),
|
|
735
|
+
depth: zNum().describe("Maximum depth of the dependency graph"),
|
|
736
|
+
output_format: z.enum(["json", "mermaid"]).optional().describe("Output format: 'json' (default) or 'mermaid' (dependency diagram)"),
|
|
737
|
+
},
|
|
738
|
+
handler: async (args) => {
|
|
739
|
+
const result = await getKnowledgeMap(args.repo, args.focus, args.depth, args.output_format);
|
|
740
|
+
return formatKnowledgeMap(result);
|
|
741
|
+
},
|
|
742
|
+
},
|
|
743
|
+
// --- Diff ---
|
|
744
|
+
{
|
|
745
|
+
name: "diff_outline",
|
|
746
|
+
category: "diff",
|
|
747
|
+
searchHint: "diff outline structural changes git refs compare",
|
|
748
|
+
description: "Get a structural outline of what changed between two git refs",
|
|
749
|
+
schema: {
|
|
750
|
+
repo: z.string().describe("Repository identifier"),
|
|
751
|
+
since: z.string().describe("Git ref to compare from"),
|
|
752
|
+
until: z.string().optional().describe("Git ref to compare to (defaults to HEAD)"),
|
|
753
|
+
},
|
|
754
|
+
handler: async (args) => {
|
|
755
|
+
const result = await diffOutline(args.repo, args.since, args.until);
|
|
756
|
+
return formatDiffOutline(result);
|
|
757
|
+
},
|
|
758
|
+
},
|
|
759
|
+
{
|
|
760
|
+
name: "changed_symbols",
|
|
761
|
+
category: "diff",
|
|
762
|
+
searchHint: "changed symbols added modified removed git diff",
|
|
763
|
+
description: "List symbols that were added, modified, or removed between two git refs",
|
|
764
|
+
schema: {
|
|
765
|
+
repo: z.string().describe("Repository identifier"),
|
|
766
|
+
since: z.string().describe("Git ref to compare from"),
|
|
767
|
+
until: z.string().optional().describe("Git ref to compare to (defaults to HEAD)"),
|
|
768
|
+
include_diff: zBool().describe("Include unified diff per changed file (truncated to 500 chars)"),
|
|
769
|
+
},
|
|
770
|
+
handler: async (args) => {
|
|
771
|
+
const opts = {};
|
|
772
|
+
if (args.include_diff === true)
|
|
773
|
+
opts.include_diff = true;
|
|
774
|
+
const result = await changedSymbols(args.repo, args.since, args.until, opts);
|
|
775
|
+
return formatChangedSymbols(result);
|
|
776
|
+
},
|
|
777
|
+
},
|
|
778
|
+
// --- Generation ---
|
|
779
|
+
{
|
|
780
|
+
name: "generate_claude_md",
|
|
781
|
+
category: "reporting",
|
|
782
|
+
searchHint: "generate CLAUDE.md project summary documentation",
|
|
783
|
+
description: "Generate a CLAUDE.md project summary file from the repository index",
|
|
784
|
+
schema: {
|
|
785
|
+
repo: z.string().describe("Repository identifier"),
|
|
786
|
+
output_path: z.string().optional().describe("Custom output file path"),
|
|
787
|
+
},
|
|
788
|
+
handler: (args) => generateClaudeMd(args.repo, args.output_path),
|
|
789
|
+
},
|
|
790
|
+
// --- Batch retrieval ---
|
|
791
|
+
{
|
|
792
|
+
name: "codebase_retrieval",
|
|
793
|
+
category: "search",
|
|
794
|
+
searchHint: "batch retrieval multi-query semantic hybrid token budget",
|
|
795
|
+
outputSchema: OutputSchemas.batchResults,
|
|
796
|
+
description: "Batch multi-query retrieval with shared token budget. Supports symbols/text/semantic/hybrid.",
|
|
797
|
+
schema: {
|
|
798
|
+
repo: z.string().describe("Repository identifier"),
|
|
799
|
+
queries: z
|
|
800
|
+
.union([
|
|
801
|
+
z.array(z.object({ type: z.string() }).passthrough()),
|
|
802
|
+
z.string().transform((s) => JSON.parse(s)),
|
|
803
|
+
])
|
|
804
|
+
.describe("Sub-queries array (symbols/text/file_tree/outline/references/call_chain/impact/context/knowledge_map). JSON string OK."),
|
|
805
|
+
token_budget: zNum().describe("Maximum total tokens across all sub-query results"),
|
|
806
|
+
},
|
|
807
|
+
handler: async (args) => {
|
|
808
|
+
const result = await codebaseRetrieval(args.repo, args.queries, args.token_budget);
|
|
809
|
+
// Format as text sections instead of JSON envelope
|
|
810
|
+
const sections = result.results.map((r) => {
|
|
811
|
+
const dataStr = typeof r.data === "string" ? r.data : JSON.stringify(r.data);
|
|
812
|
+
return `--- ${r.type} ---\n${dataStr}`;
|
|
813
|
+
});
|
|
814
|
+
let output = sections.join("\n\n");
|
|
815
|
+
if (result.truncated)
|
|
816
|
+
output += "\n\n(truncated: token budget exceeded)";
|
|
817
|
+
return output;
|
|
818
|
+
},
|
|
819
|
+
},
|
|
820
|
+
// --- Analysis ---
|
|
821
|
+
{
|
|
822
|
+
name: "find_dead_code",
|
|
823
|
+
category: "analysis",
|
|
824
|
+
searchHint: "dead code unused exports unreferenced symbols cleanup",
|
|
825
|
+
outputSchema: OutputSchemas.deadCode,
|
|
826
|
+
description: "Find dead code: exported symbols with zero external references.",
|
|
827
|
+
schema: {
|
|
828
|
+
repo: z.string().describe("Repository identifier"),
|
|
829
|
+
file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
|
|
830
|
+
include_tests: zBool().describe("Include test files in scan (default: false)"),
|
|
831
|
+
},
|
|
832
|
+
handler: async (args) => {
|
|
833
|
+
const result = await findDeadCode(args.repo, {
|
|
834
|
+
file_pattern: args.file_pattern,
|
|
835
|
+
include_tests: args.include_tests,
|
|
836
|
+
});
|
|
837
|
+
return formatDeadCode(result);
|
|
838
|
+
},
|
|
839
|
+
},
|
|
840
|
+
{
|
|
841
|
+
name: "find_unused_imports",
|
|
842
|
+
category: "analysis",
|
|
843
|
+
searchHint: "unused imports dead cleanup lint",
|
|
844
|
+
description: "Find imported names never referenced in the file body. Complements find_dead_code.",
|
|
845
|
+
schema: {
|
|
846
|
+
repo: z.string().describe("Repository identifier"),
|
|
847
|
+
file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
|
|
848
|
+
include_tests: zBool().describe("Include test files in scan (default: false)"),
|
|
849
|
+
},
|
|
850
|
+
handler: async (args) => {
|
|
851
|
+
const { findUnusedImports } = await import("./tools/symbol-tools.js");
|
|
852
|
+
const opts = {};
|
|
853
|
+
if (args.file_pattern != null)
|
|
854
|
+
opts.file_pattern = args.file_pattern;
|
|
855
|
+
if (args.include_tests != null)
|
|
856
|
+
opts.include_tests = args.include_tests;
|
|
857
|
+
const result = await findUnusedImports(args.repo, opts);
|
|
858
|
+
if (result.unused.length === 0) {
|
|
859
|
+
return `No unused imports found (scanned ${result.scanned_files} files)`;
|
|
860
|
+
}
|
|
861
|
+
const lines = [`${result.unused.length} unused imports (${result.scanned_files} files scanned)${result.truncated ? " [truncated]" : ""}:\n`];
|
|
862
|
+
for (const u of result.unused) {
|
|
863
|
+
lines.push(` ${u.file}:${u.line} — "${u.imported_name}"`);
|
|
864
|
+
}
|
|
865
|
+
return lines.join("\n");
|
|
866
|
+
},
|
|
867
|
+
},
|
|
868
|
+
{
|
|
869
|
+
name: "analyze_complexity",
|
|
870
|
+
category: "analysis",
|
|
871
|
+
searchHint: "complexity cyclomatic nesting refactoring functions",
|
|
872
|
+
outputSchema: OutputSchemas.complexity,
|
|
873
|
+
description: "Top N most complex functions by cyclomatic complexity, nesting, lines.",
|
|
874
|
+
schema: {
|
|
875
|
+
repo: z.string().describe("Repository identifier"),
|
|
876
|
+
file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
|
|
877
|
+
top_n: zNum().describe("Return top N most complex functions (default: 30)"),
|
|
878
|
+
min_complexity: zNum().describe("Minimum cyclomatic complexity to include (default: 1)"),
|
|
879
|
+
include_tests: zBool().describe("Include test files (default: false)"),
|
|
880
|
+
},
|
|
881
|
+
handler: async (args) => {
|
|
882
|
+
const result = await analyzeComplexity(args.repo, {
|
|
883
|
+
file_pattern: args.file_pattern,
|
|
884
|
+
top_n: args.top_n,
|
|
885
|
+
min_complexity: args.min_complexity,
|
|
886
|
+
include_tests: args.include_tests,
|
|
887
|
+
});
|
|
888
|
+
return formatComplexity(result);
|
|
889
|
+
},
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
name: "find_clones",
|
|
893
|
+
category: "analysis",
|
|
894
|
+
searchHint: "code clones duplicates copy-paste detection similar functions",
|
|
895
|
+
outputSchema: OutputSchemas.clones,
|
|
896
|
+
description: "Find code clones: similar function pairs via hash bucketing + line-similarity.",
|
|
897
|
+
schema: {
|
|
898
|
+
repo: z.string().describe("Repository identifier"),
|
|
899
|
+
file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
|
|
900
|
+
min_similarity: zNum().describe("Minimum similarity threshold 0-1 (default: 0.7)"),
|
|
901
|
+
min_lines: zNum().describe("Minimum normalized lines to consider (default: 10)"),
|
|
902
|
+
include_tests: zBool().describe("Include test files (default: false)"),
|
|
903
|
+
},
|
|
904
|
+
handler: async (args) => {
|
|
905
|
+
const result = await findClones(args.repo, {
|
|
906
|
+
file_pattern: args.file_pattern,
|
|
907
|
+
min_similarity: args.min_similarity,
|
|
908
|
+
min_lines: args.min_lines,
|
|
909
|
+
include_tests: args.include_tests,
|
|
910
|
+
});
|
|
911
|
+
return formatClones(result);
|
|
912
|
+
},
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
name: "frequency_analysis",
|
|
916
|
+
category: "analysis",
|
|
917
|
+
searchHint: "frequency analysis common patterns AST shape clusters",
|
|
918
|
+
description: "Group functions by normalized AST shape. Finds emergent patterns invisible to regex.",
|
|
919
|
+
schema: {
|
|
920
|
+
repo: z.string().describe("Repository identifier"),
|
|
921
|
+
top_n: zNum().optional().describe("Number of clusters to return (default: 30)"),
|
|
922
|
+
min_nodes: zNum().optional().describe("Minimum AST nodes in a subtree to include (default: 5)"),
|
|
923
|
+
file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
|
|
924
|
+
kind: z.string().optional().describe("Filter by symbol kind, comma-separated (default: function,method)"),
|
|
925
|
+
include_tests: zBool().describe("Include test files (default: false)"),
|
|
926
|
+
token_budget: zNum().optional().describe("Max tokens for response"),
|
|
927
|
+
},
|
|
928
|
+
handler: async (args) => frequencyAnalysis(args.repo, {
|
|
929
|
+
top_n: args.top_n,
|
|
930
|
+
min_nodes: args.min_nodes,
|
|
931
|
+
file_pattern: args.file_pattern,
|
|
932
|
+
kind: args.kind,
|
|
933
|
+
include_tests: args.include_tests,
|
|
934
|
+
token_budget: args.token_budget,
|
|
935
|
+
}),
|
|
936
|
+
},
|
|
937
|
+
{
|
|
938
|
+
name: "analyze_hotspots",
|
|
939
|
+
category: "analysis",
|
|
940
|
+
searchHint: "hotspots git churn bug-prone change frequency complexity",
|
|
941
|
+
description: "Git churn hotspots: change frequency × complexity. Higher score = more bug-prone.",
|
|
942
|
+
schema: {
|
|
943
|
+
repo: z.string().describe("Repository identifier"),
|
|
944
|
+
since_days: zNum().describe("Look back N days (default: 90)"),
|
|
945
|
+
top_n: zNum().describe("Return top N hotspots (default: 30)"),
|
|
946
|
+
file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
|
|
947
|
+
},
|
|
948
|
+
handler: async (args) => {
|
|
949
|
+
const result = await analyzeHotspots(args.repo, {
|
|
950
|
+
since_days: args.since_days,
|
|
951
|
+
top_n: args.top_n,
|
|
952
|
+
file_pattern: args.file_pattern,
|
|
953
|
+
});
|
|
954
|
+
return formatHotspots(result);
|
|
955
|
+
},
|
|
956
|
+
},
|
|
957
|
+
// --- Cross-repo ---
|
|
958
|
+
{
|
|
959
|
+
name: "cross_repo_search",
|
|
960
|
+
category: "cross-repo",
|
|
961
|
+
searchHint: "cross-repo search symbols across all repositories monorepo microservice",
|
|
962
|
+
description: "Search symbols across ALL indexed repositories. Useful for monorepos and microservice architectures.",
|
|
963
|
+
schema: {
|
|
964
|
+
query: z.string().describe("Symbol search query"),
|
|
965
|
+
repo_pattern: z.string().optional().describe("Filter repos by name pattern (e.g. 'local/tgm')"),
|
|
966
|
+
kind: z.string().optional().describe("Filter by symbol kind"),
|
|
967
|
+
top_k: zNum().describe("Max results per repo (default: 10)"),
|
|
968
|
+
include_source: zBool().describe("Include source code"),
|
|
969
|
+
},
|
|
970
|
+
handler: (args) => crossRepoSearchSymbols(args.query, {
|
|
971
|
+
repo_pattern: args.repo_pattern,
|
|
972
|
+
kind: args.kind,
|
|
973
|
+
top_k: args.top_k,
|
|
974
|
+
include_source: args.include_source,
|
|
975
|
+
}),
|
|
976
|
+
},
|
|
977
|
+
{
|
|
978
|
+
name: "cross_repo_refs",
|
|
979
|
+
category: "cross-repo",
|
|
980
|
+
searchHint: "cross-repo references symbol across all repositories",
|
|
981
|
+
description: "Find references to a symbol across ALL indexed repositories.",
|
|
982
|
+
schema: {
|
|
983
|
+
symbol_name: z.string().describe("Symbol name to find references for"),
|
|
984
|
+
repo_pattern: z.string().optional().describe("Filter repos by name pattern"),
|
|
985
|
+
file_pattern: z.string().optional().describe("Filter files by glob pattern"),
|
|
986
|
+
},
|
|
987
|
+
handler: (args) => crossRepoFindReferences(args.symbol_name, {
|
|
988
|
+
repo_pattern: args.repo_pattern,
|
|
989
|
+
file_pattern: args.file_pattern,
|
|
990
|
+
}),
|
|
991
|
+
},
|
|
992
|
+
// --- Patterns ---
|
|
993
|
+
{
|
|
994
|
+
name: "search_patterns",
|
|
995
|
+
category: "patterns",
|
|
996
|
+
searchHint: "search patterns anti-patterns CQ violations useEffect empty-catch console-log",
|
|
997
|
+
description: "Search structural patterns/anti-patterns. Built-in or custom regex.",
|
|
998
|
+
schema: {
|
|
999
|
+
repo: z.string().describe("Repository identifier"),
|
|
1000
|
+
pattern: z.string().describe("Built-in pattern name or custom regex"),
|
|
1001
|
+
file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
|
|
1002
|
+
include_tests: zBool().describe("Include test files (default: false)"),
|
|
1003
|
+
max_results: zNum().describe("Max results (default: 50)"),
|
|
1004
|
+
},
|
|
1005
|
+
handler: async (args) => {
|
|
1006
|
+
const result = await searchPatterns(args.repo, args.pattern, {
|
|
1007
|
+
file_pattern: args.file_pattern,
|
|
1008
|
+
include_tests: args.include_tests,
|
|
1009
|
+
max_results: args.max_results,
|
|
1010
|
+
});
|
|
1011
|
+
return formatSearchPatterns(result);
|
|
1012
|
+
},
|
|
1013
|
+
},
|
|
1014
|
+
{
|
|
1015
|
+
name: "list_patterns",
|
|
1016
|
+
category: "patterns",
|
|
1017
|
+
searchHint: "list available built-in patterns anti-patterns",
|
|
1018
|
+
description: "List all available built-in structural code patterns for search_patterns.",
|
|
1019
|
+
schema: {},
|
|
1020
|
+
handler: async () => listPatterns(),
|
|
1021
|
+
},
|
|
1022
|
+
// --- Report ---
|
|
1023
|
+
{
|
|
1024
|
+
name: "generate_report",
|
|
1025
|
+
category: "reporting",
|
|
1026
|
+
searchHint: "generate HTML report complexity dead code hotspots architecture browser",
|
|
1027
|
+
description: "Generate a standalone HTML report with complexity, dead code, hotspots, and architecture. Opens in any browser.",
|
|
1028
|
+
schema: {
|
|
1029
|
+
repo: z.string().describe("Repository identifier"),
|
|
1030
|
+
},
|
|
1031
|
+
handler: (args) => generateReport(args.repo),
|
|
1032
|
+
},
|
|
1033
|
+
// --- Conversations ---
|
|
1034
|
+
{
|
|
1035
|
+
name: "index_conversations",
|
|
1036
|
+
category: "conversations",
|
|
1037
|
+
searchHint: "index conversations Claude Code history JSONL",
|
|
1038
|
+
description: "Index Claude Code conversation history for search. Scans JSONL files in ~/.claude/projects/ for the given project path.",
|
|
1039
|
+
schema: {
|
|
1040
|
+
project_path: z.string().optional().describe("Path to the Claude project conversations directory. Auto-detects from cwd if omitted."),
|
|
1041
|
+
quiet: zBool().describe("Suppress output (used by session-end hook)"),
|
|
1042
|
+
},
|
|
1043
|
+
handler: async (args) => indexConversations(args.project_path),
|
|
1044
|
+
},
|
|
1045
|
+
{
|
|
1046
|
+
name: "search_conversations",
|
|
1047
|
+
category: "conversations",
|
|
1048
|
+
searchHint: "search conversations past sessions history BM25 semantic",
|
|
1049
|
+
description: "Search conversations in one project (BM25+semantic). For all projects: search_all_conversations.",
|
|
1050
|
+
schema: {
|
|
1051
|
+
query: z.string().describe("Search query — keywords or natural language"),
|
|
1052
|
+
project: z.string().optional().describe("Project path to search (default: current project)"),
|
|
1053
|
+
limit: zNum().optional().describe("Maximum results to return (default: 10, max: 50)"),
|
|
1054
|
+
},
|
|
1055
|
+
handler: async (args) => {
|
|
1056
|
+
const result = await searchConversations(args.query, args.project, args.limit);
|
|
1057
|
+
return formatConversations(result);
|
|
1058
|
+
},
|
|
1059
|
+
},
|
|
1060
|
+
{
|
|
1061
|
+
name: "find_conversations_for_symbol",
|
|
1062
|
+
category: "conversations",
|
|
1063
|
+
searchHint: "find conversations symbol discussion cross-reference code",
|
|
1064
|
+
description: "Find conversations that discussed a code symbol. Cross-refs code + history.",
|
|
1065
|
+
schema: {
|
|
1066
|
+
symbol_name: z.string().describe("Name of the code symbol to search for in conversations"),
|
|
1067
|
+
repo: z.string().describe("Code repository to resolve the symbol from (e.g., 'local/my-project')"),
|
|
1068
|
+
limit: zNum().optional().describe("Maximum conversation results (default: 5)"),
|
|
1069
|
+
},
|
|
1070
|
+
handler: async (args) => {
|
|
1071
|
+
const result = await findConversationsForSymbol(args.symbol_name, args.repo, args.limit);
|
|
1072
|
+
return formatConversations(result);
|
|
1073
|
+
},
|
|
1074
|
+
},
|
|
1075
|
+
{
|
|
1076
|
+
name: "search_all_conversations",
|
|
1077
|
+
category: "conversations",
|
|
1078
|
+
searchHint: "search all conversations every project cross-project",
|
|
1079
|
+
description: "Search ALL conversation projects at once, ranked by relevance.",
|
|
1080
|
+
schema: {
|
|
1081
|
+
query: z.string().describe("Search query — keywords, natural language, or concept"),
|
|
1082
|
+
limit: zNum().optional().describe("Maximum results across all projects (default: 10)"),
|
|
1083
|
+
},
|
|
1084
|
+
handler: async (args) => {
|
|
1085
|
+
const result = await searchAllConversations(args.query, args.limit);
|
|
1086
|
+
return formatConversations(result);
|
|
1087
|
+
},
|
|
1088
|
+
},
|
|
1089
|
+
// --- Security ---
|
|
1090
|
+
{
|
|
1091
|
+
name: "scan_secrets",
|
|
1092
|
+
category: "security",
|
|
1093
|
+
searchHint: "scan secrets API keys tokens passwords credentials security",
|
|
1094
|
+
outputSchema: OutputSchemas.secrets,
|
|
1095
|
+
description: "Scan for hardcoded secrets (API keys, tokens, passwords). ~1,100 rules.",
|
|
1096
|
+
schema: {
|
|
1097
|
+
repo: z.string().describe("Repository identifier"),
|
|
1098
|
+
file_pattern: z.string().optional().describe("Glob pattern to filter scanned files"),
|
|
1099
|
+
min_confidence: z.enum(["high", "medium", "low"]).optional().describe("Minimum confidence level (default: medium)"),
|
|
1100
|
+
exclude_tests: zBool().describe("Exclude test file findings (default: true)"),
|
|
1101
|
+
severity: z.enum(["critical", "high", "medium", "low"]).optional().describe("Minimum severity level"),
|
|
1102
|
+
},
|
|
1103
|
+
handler: async (args) => {
|
|
1104
|
+
const result = await scanSecrets(args.repo, {
|
|
1105
|
+
file_pattern: args.file_pattern,
|
|
1106
|
+
min_confidence: args.min_confidence,
|
|
1107
|
+
exclude_tests: args.exclude_tests,
|
|
1108
|
+
severity: args.severity,
|
|
1109
|
+
});
|
|
1110
|
+
return formatSecrets(result);
|
|
1111
|
+
},
|
|
1112
|
+
},
|
|
1113
|
+
// --- Memory consolidation ---
|
|
1114
|
+
{
|
|
1115
|
+
name: "consolidate_memories",
|
|
1116
|
+
category: "conversations",
|
|
1117
|
+
searchHint: "consolidate memories dream knowledge MEMORY.md decisions solutions patterns",
|
|
1118
|
+
description: "Consolidate conversations into MEMORY.md — decisions, solutions, patterns.",
|
|
1119
|
+
schema: {
|
|
1120
|
+
project_path: z.string().optional().describe("Project path (auto-detects from cwd if omitted)"),
|
|
1121
|
+
output_path: z.string().optional().describe("Custom output file path (default: MEMORY.md in project root)"),
|
|
1122
|
+
min_confidence: z.enum(["high", "medium", "low"]).optional().describe("Minimum confidence level for extracted memories (default: low)"),
|
|
1123
|
+
},
|
|
1124
|
+
handler: async (args) => {
|
|
1125
|
+
const opts = {};
|
|
1126
|
+
if (typeof args.output_path === "string")
|
|
1127
|
+
opts.output_path = args.output_path;
|
|
1128
|
+
if (typeof args.min_confidence === "string")
|
|
1129
|
+
opts.min_confidence = args.min_confidence;
|
|
1130
|
+
const result = await consolidateMemories(args.project_path, opts);
|
|
1131
|
+
return result;
|
|
1132
|
+
},
|
|
1133
|
+
},
|
|
1134
|
+
{
|
|
1135
|
+
name: "read_memory",
|
|
1136
|
+
category: "conversations",
|
|
1137
|
+
searchHint: "read memory MEMORY.md institutional knowledge past decisions",
|
|
1138
|
+
description: "Read MEMORY.md knowledge file with past decisions and patterns.",
|
|
1139
|
+
schema: {
|
|
1140
|
+
project_path: z.string().optional().describe("Project path (default: current directory)"),
|
|
1141
|
+
},
|
|
1142
|
+
handler: async (args) => {
|
|
1143
|
+
const result = await readMemory(args.project_path);
|
|
1144
|
+
if (!result)
|
|
1145
|
+
return { error: "No MEMORY.md found. Run consolidate_memories first." };
|
|
1146
|
+
return result.content;
|
|
1147
|
+
},
|
|
1148
|
+
},
|
|
1149
|
+
// --- Coordinator ---
|
|
1150
|
+
{
|
|
1151
|
+
name: "create_analysis_plan",
|
|
1152
|
+
category: "meta",
|
|
1153
|
+
searchHint: "create plan multi-step analysis workflow coordinator scratchpad",
|
|
1154
|
+
description: "Create multi-step analysis plan with shared scratchpad and dependencies.",
|
|
1155
|
+
schema: {
|
|
1156
|
+
title: z.string().describe("Plan title describing the analysis goal"),
|
|
1157
|
+
steps: z.union([
|
|
1158
|
+
z.array(z.object({
|
|
1159
|
+
description: z.string(),
|
|
1160
|
+
tool: z.string(),
|
|
1161
|
+
args: z.record(z.string(), z.unknown()),
|
|
1162
|
+
result_key: z.string().optional(),
|
|
1163
|
+
depends_on: z.array(z.string()).optional(),
|
|
1164
|
+
})),
|
|
1165
|
+
z.string().transform((s) => JSON.parse(s)),
|
|
1166
|
+
]).describe("Steps array: {description, tool, args, result_key?, depends_on?}. JSON string OK."),
|
|
1167
|
+
},
|
|
1168
|
+
handler: async (args) => {
|
|
1169
|
+
const result = await createAnalysisPlan(args.title, args.steps);
|
|
1170
|
+
return result;
|
|
1171
|
+
},
|
|
1172
|
+
},
|
|
1173
|
+
{
|
|
1174
|
+
name: "scratchpad_write",
|
|
1175
|
+
category: "meta",
|
|
1176
|
+
searchHint: "scratchpad write store knowledge cross-step data persist",
|
|
1177
|
+
description: "Write key-value to plan scratchpad for cross-step knowledge sharing.",
|
|
1178
|
+
schema: {
|
|
1179
|
+
plan_id: z.string().describe("Analysis plan identifier"),
|
|
1180
|
+
key: z.string().describe("Key name for the entry"),
|
|
1181
|
+
value: z.string().describe("Value to store"),
|
|
1182
|
+
},
|
|
1183
|
+
handler: async (args) => writeScratchpad(args.plan_id, args.key, args.value),
|
|
1184
|
+
},
|
|
1185
|
+
{
|
|
1186
|
+
name: "scratchpad_read",
|
|
1187
|
+
category: "meta",
|
|
1188
|
+
searchHint: "scratchpad read retrieve knowledge entry",
|
|
1189
|
+
description: "Read a key from a plan's scratchpad. Returns the stored value or null if not found.",
|
|
1190
|
+
schema: {
|
|
1191
|
+
plan_id: z.string().describe("Analysis plan identifier"),
|
|
1192
|
+
key: z.string().describe("Key name to read"),
|
|
1193
|
+
},
|
|
1194
|
+
handler: async (args) => {
|
|
1195
|
+
const result = await readScratchpad(args.plan_id, args.key);
|
|
1196
|
+
return result ?? { error: "Key not found in scratchpad" };
|
|
1197
|
+
},
|
|
1198
|
+
},
|
|
1199
|
+
{
|
|
1200
|
+
name: "scratchpad_list",
|
|
1201
|
+
category: "meta",
|
|
1202
|
+
searchHint: "scratchpad list entries keys",
|
|
1203
|
+
description: "List all entries in a plan's scratchpad with their sizes.",
|
|
1204
|
+
schema: {
|
|
1205
|
+
plan_id: z.string().describe("Analysis plan identifier"),
|
|
1206
|
+
},
|
|
1207
|
+
handler: (args) => listScratchpad(args.plan_id),
|
|
1208
|
+
},
|
|
1209
|
+
{
|
|
1210
|
+
name: "update_step_status",
|
|
1211
|
+
category: "meta",
|
|
1212
|
+
searchHint: "update step status plan progress completed failed",
|
|
1213
|
+
description: "Update step status in plan. Auto-updates plan status on completion.",
|
|
1214
|
+
schema: {
|
|
1215
|
+
plan_id: z.string().describe("Analysis plan identifier"),
|
|
1216
|
+
step_id: z.string().describe("Step identifier (e.g. step_1)"),
|
|
1217
|
+
status: z.enum(["pending", "in_progress", "completed", "failed", "skipped"]).describe("New status for the step"),
|
|
1218
|
+
error: z.string().optional().describe("Error message if status is 'failed'"),
|
|
1219
|
+
},
|
|
1220
|
+
handler: async (args) => {
|
|
1221
|
+
const result = await updateStepStatus(args.plan_id, args.step_id, args.status, args.error);
|
|
1222
|
+
return result;
|
|
1223
|
+
},
|
|
1224
|
+
},
|
|
1225
|
+
{
|
|
1226
|
+
name: "get_analysis_plan",
|
|
1227
|
+
category: "meta",
|
|
1228
|
+
searchHint: "get plan status steps progress",
|
|
1229
|
+
description: "Get the current state of an analysis plan including all step statuses.",
|
|
1230
|
+
schema: {
|
|
1231
|
+
plan_id: z.string().describe("Analysis plan identifier"),
|
|
1232
|
+
},
|
|
1233
|
+
handler: async (args) => {
|
|
1234
|
+
const plan = getPlan(args.plan_id);
|
|
1235
|
+
return plan ?? { error: "Plan not found" };
|
|
1236
|
+
},
|
|
1237
|
+
},
|
|
1238
|
+
{
|
|
1239
|
+
name: "list_analysis_plans",
|
|
1240
|
+
category: "meta",
|
|
1241
|
+
searchHint: "list plans active analysis workflows",
|
|
1242
|
+
description: "List all active analysis plans with their completion status.",
|
|
1243
|
+
schema: {},
|
|
1244
|
+
handler: async () => listPlans(),
|
|
1245
|
+
},
|
|
1246
|
+
// --- Review diff ---
|
|
1247
|
+
{
|
|
1248
|
+
name: "review_diff",
|
|
1249
|
+
category: "diff",
|
|
1250
|
+
searchHint: "review diff static analysis git changes secrets breaking-changes complexity dead-code blast-radius",
|
|
1251
|
+
description: "Run 9 parallel static analysis checks on a git diff: secrets, breaking changes, coupling gaps, complexity, dead-code, blast-radius, bug-patterns, test-gaps, hotspots. Returns a scored verdict (pass/warn/fail) with tiered findings.",
|
|
1252
|
+
schema: {
|
|
1253
|
+
repo: z.string().describe("Repository identifier"),
|
|
1254
|
+
since: z.string().optional().describe("Base git ref (default: HEAD~1)"),
|
|
1255
|
+
until: z.string().optional().describe("Target ref. Default: HEAD. Special: WORKING, STAGED"),
|
|
1256
|
+
checks: z.string().optional().describe("Comma-separated check names (default: all)"),
|
|
1257
|
+
exclude_patterns: z.string().optional().describe("Comma-separated globs to exclude"),
|
|
1258
|
+
token_budget: zNum().describe("Max tokens (default: 15000)"),
|
|
1259
|
+
max_files: zNum().describe("Warn above N files (default: 50)"),
|
|
1260
|
+
check_timeout_ms: zNum().describe("Per-check timeout ms (default: 8000)"),
|
|
1261
|
+
},
|
|
1262
|
+
handler: async (args) => {
|
|
1263
|
+
const checksArr = args.checks
|
|
1264
|
+
? args.checks.split(",").map((c) => c.trim()).filter(Boolean)
|
|
1265
|
+
: undefined;
|
|
1266
|
+
const excludeArr = args.exclude_patterns
|
|
1267
|
+
? args.exclude_patterns.split(",").map((p) => p.trim()).filter(Boolean)
|
|
1268
|
+
: undefined;
|
|
1269
|
+
const opts = {
|
|
1270
|
+
repo: args.repo,
|
|
1271
|
+
};
|
|
1272
|
+
if (args.since != null)
|
|
1273
|
+
opts.since = args.since;
|
|
1274
|
+
if (args.until != null)
|
|
1275
|
+
opts.until = args.until;
|
|
1276
|
+
if (checksArr != null)
|
|
1277
|
+
opts.checks = checksArr.join(",");
|
|
1278
|
+
if (excludeArr != null)
|
|
1279
|
+
opts.exclude_patterns = excludeArr;
|
|
1280
|
+
if (args.token_budget != null)
|
|
1281
|
+
opts.token_budget = args.token_budget;
|
|
1282
|
+
if (args.max_files != null)
|
|
1283
|
+
opts.max_files = args.max_files;
|
|
1284
|
+
if (args.check_timeout_ms != null)
|
|
1285
|
+
opts.check_timeout_ms = args.check_timeout_ms;
|
|
1286
|
+
const result = await reviewDiff(args.repo, opts);
|
|
1287
|
+
return formatReviewDiff(result);
|
|
1288
|
+
},
|
|
1289
|
+
},
|
|
1290
|
+
// --- Stats ---
|
|
1291
|
+
{
|
|
1292
|
+
name: "usage_stats",
|
|
1293
|
+
category: "meta",
|
|
1294
|
+
searchHint: "usage statistics tool calls tokens timing metrics",
|
|
1295
|
+
outputSchema: OutputSchemas.usageStats,
|
|
1296
|
+
description: "Show usage statistics for all CodeSift tool calls (call counts, tokens, timing, repos)",
|
|
1297
|
+
schema: {},
|
|
1298
|
+
handler: async () => {
|
|
1299
|
+
const stats = await getUsageStats();
|
|
1300
|
+
return { report: formatUsageReport(stats) };
|
|
1301
|
+
},
|
|
1302
|
+
},
|
|
1303
|
+
];
|
|
1304
|
+
function buildToolSummaries() {
|
|
1305
|
+
return TOOL_DEFINITIONS.map((t) => ({
|
|
1306
|
+
name: t.name,
|
|
1307
|
+
category: t.category,
|
|
1308
|
+
description: t.description,
|
|
1309
|
+
searchHint: t.searchHint,
|
|
1310
|
+
}));
|
|
1311
|
+
}
|
|
1312
|
+
/**
|
|
1313
|
+
* Extract structured param info from a ToolDefinition's Zod schema.
|
|
1314
|
+
*/
|
|
1315
|
+
function extractToolParams(def) {
|
|
1316
|
+
return Object.entries(def.schema).map(([key, val]) => {
|
|
1317
|
+
const zodVal = val;
|
|
1318
|
+
const isOptional = zodVal.isOptional?.() ?? false;
|
|
1319
|
+
return {
|
|
1320
|
+
name: key,
|
|
1321
|
+
required: !isOptional,
|
|
1322
|
+
description: zodVal.description ?? "",
|
|
1323
|
+
};
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Return full param details for a specific list of tool names.
|
|
1328
|
+
* Unknown names are collected in not_found.
|
|
1329
|
+
*/
|
|
1330
|
+
export function describeTools(names) {
|
|
1331
|
+
const capped = names.slice(0, 100); // CQ6 cap
|
|
1332
|
+
const tools = [];
|
|
1333
|
+
const not_found = [];
|
|
1334
|
+
for (const name of capped) {
|
|
1335
|
+
const def = TOOL_DEFINITIONS.find((t) => t.name === name);
|
|
1336
|
+
if (!def) {
|
|
1337
|
+
not_found.push(name);
|
|
1338
|
+
continue;
|
|
1339
|
+
}
|
|
1340
|
+
tools.push({
|
|
1341
|
+
name: def.name,
|
|
1342
|
+
category: def.category ?? "uncategorized",
|
|
1343
|
+
description: def.description,
|
|
1344
|
+
is_core: CORE_TOOL_NAMES.has(def.name),
|
|
1345
|
+
params: extractToolParams(def),
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
return { tools, not_found };
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Search tool catalog by keyword. Returns matching tools with descriptions.
|
|
1352
|
+
* Uses simple token matching against name + description + searchHint + category.
|
|
1353
|
+
*/
|
|
1354
|
+
export function discoverTools(query, category) {
|
|
1355
|
+
const summaries = buildToolSummaries();
|
|
1356
|
+
const queryTokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
1357
|
+
const categories = [...new Set(summaries.map((s) => s.category).filter(Boolean))];
|
|
1358
|
+
let filtered = summaries;
|
|
1359
|
+
if (category) {
|
|
1360
|
+
filtered = filtered.filter((s) => s.category === category);
|
|
1361
|
+
}
|
|
1362
|
+
// Score each tool by keyword match
|
|
1363
|
+
const scored = filtered.map((tool) => {
|
|
1364
|
+
const searchable = `${tool.name} ${tool.description} ${tool.searchHint ?? ""} ${tool.category ?? ""}`.toLowerCase();
|
|
1365
|
+
let score = 0;
|
|
1366
|
+
for (const token of queryTokens) {
|
|
1367
|
+
if (searchable.includes(token))
|
|
1368
|
+
score++;
|
|
1369
|
+
// Bonus for name match
|
|
1370
|
+
if (tool.name.includes(token))
|
|
1371
|
+
score += 2;
|
|
1372
|
+
}
|
|
1373
|
+
// If no query tokens, match everything (category-only filter)
|
|
1374
|
+
if (queryTokens.length === 0)
|
|
1375
|
+
score = 1;
|
|
1376
|
+
return { tool, score };
|
|
1377
|
+
});
|
|
1378
|
+
const matches = scored
|
|
1379
|
+
.filter((s) => s.score > 0)
|
|
1380
|
+
.sort((a, b) => b.score - a.score)
|
|
1381
|
+
.slice(0, 15)
|
|
1382
|
+
.map((s) => {
|
|
1383
|
+
// Look up full definition to extract param info for deferred tools
|
|
1384
|
+
const fullDef = TOOL_DEFINITIONS.find((t) => t.name === s.tool.name);
|
|
1385
|
+
const params = fullDef
|
|
1386
|
+
? extractToolParams(fullDef).map((p) => `${p.name}${p.required ? "" : "?"}: ${p.description || "string"}`)
|
|
1387
|
+
: [];
|
|
1388
|
+
return {
|
|
1389
|
+
name: s.tool.name,
|
|
1390
|
+
category: s.tool.category ?? "uncategorized",
|
|
1391
|
+
description: s.tool.description.slice(0, 200),
|
|
1392
|
+
params: params.length > 0 ? params : undefined,
|
|
1393
|
+
is_core: CORE_TOOL_NAMES.has(s.tool.name),
|
|
1394
|
+
};
|
|
1395
|
+
});
|
|
1396
|
+
return {
|
|
1397
|
+
query,
|
|
1398
|
+
matches,
|
|
1399
|
+
total_tools: TOOL_DEFINITIONS.length,
|
|
1400
|
+
categories,
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
// ---------------------------------------------------------------------------
|
|
1404
|
+
// Registration loop
|
|
1405
|
+
// ---------------------------------------------------------------------------
|
|
1406
|
+
export function registerTools(server, options) {
|
|
1407
|
+
const deferNonCore = options?.deferNonCore ?? false;
|
|
1408
|
+
// Clear handles from any previous registration (e.g. tests calling registerTools multiple times)
|
|
1409
|
+
toolHandles.clear();
|
|
1410
|
+
// Register ALL tools with full schema; store returned handles
|
|
1411
|
+
for (const tool of TOOL_DEFINITIONS) {
|
|
1412
|
+
const handle = server.tool(tool.name, tool.description, tool.schema, async (args) => wrapTool(tool.name, args, () => tool.handler(args))());
|
|
1413
|
+
toolHandles.set(tool.name, handle);
|
|
1414
|
+
}
|
|
1415
|
+
// Always register discover_tools meta-tool
|
|
1416
|
+
const discoverHandle = server.tool("discover_tools", "Search tool catalog by keyword or category. Returns matching tools with descriptions.", {
|
|
1417
|
+
query: z.string().describe("Keywords to search for (e.g. 'dead code', 'complexity', 'rename', 'secrets')"),
|
|
1418
|
+
category: z.string().optional().describe("Filter by category (e.g. 'analysis', 'lsp', 'architecture')"),
|
|
1419
|
+
}, async (args) => wrapTool("discover_tools", args, async () => {
|
|
1420
|
+
return discoverTools(args.query, args.category);
|
|
1421
|
+
})());
|
|
1422
|
+
toolHandles.set("discover_tools", discoverHandle);
|
|
1423
|
+
// Register describe_tools meta-tool — returns full schema for specific tools by name
|
|
1424
|
+
const describeHandle = server.tool("describe_tools", "Get full schema for specific tools by name. Use after discover_tools to see params before calling.", {
|
|
1425
|
+
names: z.array(z.string()).describe("Tool names to describe"),
|
|
1426
|
+
reveal: zBool().describe("If true, enable tools in ListTools so the LLM can call them"),
|
|
1427
|
+
}, async (args) => wrapTool("describe_tools", args, async () => {
|
|
1428
|
+
const result = describeTools(args.names);
|
|
1429
|
+
if (args.reveal === true) {
|
|
1430
|
+
for (const t of result.tools) {
|
|
1431
|
+
const h = toolHandles.get(t.name);
|
|
1432
|
+
if (h)
|
|
1433
|
+
h.enable();
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
return result;
|
|
1437
|
+
})());
|
|
1438
|
+
toolHandles.set("describe_tools", describeHandle);
|
|
1439
|
+
// In deferred mode, disable non-core tools (they remain registered but hidden from ListTools).
|
|
1440
|
+
// LLM discovers them via discover_tools, then reveals with describe_tools(reveal: true).
|
|
1441
|
+
if (deferNonCore) {
|
|
1442
|
+
for (const [name, handle] of toolHandles) {
|
|
1443
|
+
if (!CORE_TOOL_NAMES.has(name) && name !== "discover_tools" && name !== "describe_tools") {
|
|
1444
|
+
handle.disable();
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
// Register progressive shorteners for analysis tools with large outputs
|
|
1449
|
+
registerShortener("analyze_complexity", { compact: formatComplexityCompact, counts: formatComplexityCounts });
|
|
1450
|
+
registerShortener("find_clones", { compact: formatClonesCompact, counts: formatClonesCounts });
|
|
1451
|
+
registerShortener("analyze_hotspots", { compact: formatHotspotsCompact, counts: formatHotspotsCounts });
|
|
1452
|
+
}
|
|
1453
|
+
//# sourceMappingURL=register-tools.js.map
|