codesift-mcp 0.5.0 → 0.5.4

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.
@@ -3,80 +3,9 @@ import { z } from "zod";
3
3
  const zBool = () => z.union([z.boolean(), z.string().transform((s) => s === "true")]).optional();
4
4
  import { wrapTool, registerShortener } from "./server-helpers.js";
5
5
  import { detectProjectLanguagesSync } from "./utils/language-detect.js";
6
- import { indexFolder, indexFile, indexRepo, listAllRepos, invalidateCache, getCodeIndex } from "./tools/index-tools.js";
7
- import { STUB_LANGUAGES } from "./parser/parser-manager.js";
8
- import { searchSymbols, searchText, semanticSearch } from "./tools/search-tools.js";
9
- import { getFileTree, getFileOutline, getRepoOutline, suggestQueries } from "./tools/outline-tools.js";
10
- import { getSymbol, getSymbols, findAndShow, findReferences, findReferencesBatch, findDeadCode, getContextBundle, formatRefsCompact, formatSymbolCompact, formatSymbolsCompact, formatBundleCompact } from "./tools/symbol-tools.js";
11
- import { traceCallChain } from "./tools/graph-tools.js";
12
- import { traceComponentTree, analyzeHooks, analyzeRenders, buildContextGraph, auditCompilerReadiness, reactQuickstart } from "./tools/react-tools.js";
13
- import { impactAnalysis } from "./tools/impact-tools.js";
14
- import { traceRoute } from "./tools/route-tools.js";
15
- import { detectCommunities } from "./tools/community-tools.js";
16
- import { assembleContext, getKnowledgeMap } from "./tools/context-tools.js";
17
- import { diffOutline, changedSymbols } from "./tools/diff-tools.js";
18
- import { generateClaudeMd } from "./tools/generate-tools.js";
19
- import { codebaseRetrieval } from "./retrieval/codebase-retrieval.js";
20
- import { analyzeComplexity } from "./tools/complexity-tools.js";
21
- import { findClones } from "./tools/clone-tools.js";
22
- import { analyzeHotspots } from "./tools/hotspot-tools.js";
23
- import { crossRepoSearchSymbols, crossRepoFindReferences } from "./tools/cross-repo-tools.js";
24
- import { searchPatterns, listPatterns } from "./tools/pattern-tools.js";
25
- import { generateReport } from "./tools/report-tools.js";
6
+ import { STUB_LANGUAGES } from "./parser/stub-languages.js";
26
7
  import { getUsageStats, formatUsageReport } from "./storage/usage-stats.js";
27
- import { goToDefinition, getTypeInfo, renameSymbol, getCallHierarchy } from "./lsp/lsp-tools.js";
28
- import { indexConversations, searchConversations, searchAllConversations, findConversationsForSymbol } from "./tools/conversation-tools.js";
29
- import { scanSecrets } from "./tools/secret-tools.js";
30
- import { resolvePhpNamespace, tracePhpEvent, findPhpViews, resolvePhpService, phpSecurityScan, phpProjectAudit, } from "./tools/php-tools.js";
31
- import { consolidateMemories, readMemory } from "./tools/memory-tools.js";
32
- import { createAnalysisPlan, writeScratchpad, readScratchpad, listScratchpad, updateStepStatus, getPlan, listPlans } from "./tools/coordinator-tools.js";
33
- import { frequencyAnalysis } from "./tools/frequency-tools.js";
34
- import { findExtensionFunctions, analyzeSealedHierarchy, traceSuspendChain, analyzeKmpDeclarations, traceFlowChain } from "./tools/kotlin-tools.js";
35
- import { traceHiltGraph } from "./tools/hilt-tools.js";
36
- import { traceComposeTree, analyzeComposeRecomposition } from "./tools/compose-tools.js";
37
- import { traceRoomSchema } from "./tools/room-tools.js";
38
- import { extractKotlinSerializationContract } from "./tools/serialization-tools.js";
39
- import { astroAnalyzeIslands, astroHydrationAudit } from "./tools/astro-islands.js";
40
- import { astroRouteMap } from "./tools/astro-routes.js";
41
- import { astroActionsAudit } from "./tools/astro-actions.js";
42
- import { astroAudit } from "./tools/astro-audit.js";
43
- import { nextjsRouteMap } from "./tools/nextjs-route-tools.js";
44
- import { nextjsMetadataAudit } from "./tools/nextjs-metadata-tools.js";
45
- import { frameworkAudit } from "./tools/nextjs-framework-audit-tools.js";
46
- import { astroConfigAnalyze } from "./tools/astro-config.js";
47
- import { astroContentCollections } from "./tools/astro-content-collections.js";
48
- import { analyzeProject, getExtractorVersions } from "./tools/project-tools.js";
49
- import { getModelGraph } from "./tools/model-tools.js";
50
- import { getTestFixtures } from "./tools/pytest-tools.js";
51
- import { findFrameworkWiring } from "./tools/wiring-tools.js";
52
- import { runRuff } from "./tools/ruff-tools.js";
53
- import { parsePyproject } from "./tools/pyproject-tools.js";
54
- import { resolveConstantValue } from "./tools/python-constants-tools.js";
55
- import { effectiveDjangoViewSecurity } from "./tools/django-view-security-tools.js";
56
- import { findPythonCallers } from "./tools/python-callers.js";
57
- import { taintTrace } from "./tools/taint-tools.js";
58
- import { analyzeDjangoSettings } from "./tools/django-settings.js";
59
- import { runMypy, runPyright } from "./tools/typecheck-tools.js";
60
- import { analyzePythonDeps } from "./tools/python-deps-analyzer.js";
61
- import { pythonAudit } from "./tools/python-audit.js";
62
- import { traceFastAPIDepends } from "./tools/fastapi-depends.js";
63
- import { analyzeAsyncCorrectness } from "./tools/async-correctness.js";
64
- import { getPydanticModels } from "./tools/pydantic-models.js";
65
- import { reviewDiff } from "./tools/review-diff-tools.js";
66
- import { auditScan } from "./tools/audit-tools.js";
67
- import { indexStatus } from "./tools/status-tools.js";
68
- import { auditAgentConfig } from "./tools/agent-config-tools.js";
69
- import { testImpactAnalysis } from "./tools/test-impact-tools.js";
70
- import { dependencyAudit } from "./tools/dependency-audit-tools.js";
71
- import { migrationLint } from "./tools/migration-lint-tools.js";
72
- import { planTurn, formatPlanTurnResult } from "./tools/plan-turn-tools.js";
73
- import { astroMigrationCheck } from "./tools/astro-migration.js";
74
- import { analyzePrismaSchema } from "./tools/prisma-schema-tools.js";
75
- import { findPerfHotspots } from "./tools/perf-tools.js";
76
- import { fanInFanOut, coChangeAnalysis } from "./tools/coupling-tools.js";
77
- import { architectureSummary } from "./tools/architecture-tools.js";
78
- import { nestAudit } from "./tools/nest-tools.js";
79
- import { explainQuery } from "./tools/query-tools.js";
8
+ import { indexFolder, indexFile, indexRepo, listAllRepos, invalidateCache, getCodeIndex, searchSymbols, searchText, semanticSearch, getFileTree, getFileOutline, getRepoOutline, suggestQueries, getSymbol, getSymbols, findAndShow, findReferences, findReferencesBatch, findDeadCode, getContextBundle, formatRefsCompact, formatSymbolCompact, formatSymbolsCompact, formatBundleCompact, traceCallChain, traceComponentTree, analyzeHooks, analyzeRenders, buildContextGraph, auditCompilerReadiness, reactQuickstart, impactAnalysis, traceRoute, detectCommunities, assembleContext, getKnowledgeMap, diffOutline, changedSymbols, generateClaudeMd, codebaseRetrieval, analyzeComplexity, findClones, analyzeHotspots, crossRepoSearchSymbols, crossRepoFindReferences, searchPatterns, listPatterns, generateReport, goToDefinition, getTypeInfo, renameSymbol, getCallHierarchy, indexConversations, searchConversations, searchAllConversations, findConversationsForSymbol, scanSecrets, resolvePhpNamespace, tracePhpEvent, findPhpViews, resolvePhpService, phpSecurityScan, phpProjectAudit, consolidateMemories, readMemory, createAnalysisPlan, writeScratchpad, readScratchpad, listScratchpad, updateStepStatus, getPlan, listPlans, frequencyAnalysis, findExtensionFunctions, analyzeSealedHierarchy, traceSuspendChain, analyzeKmpDeclarations, traceFlowChain, traceHiltGraph, traceComposeTree, analyzeComposeRecomposition, traceRoomSchema, extractKotlinSerializationContract, astroAnalyzeIslands, astroHydrationAudit, astroRouteMap, astroActionsAudit, astroAudit, nextjsRouteMap, nextjsMetadataAudit, frameworkAudit, astroConfigAnalyze, astroContentCollections, analyzeProject, getExtractorVersions, getModelGraph, getTestFixtures, findFrameworkWiring, runRuff, parsePyproject, resolveConstantValue, effectiveDjangoViewSecurity, findPythonCallers, taintTrace, analyzeDjangoSettings, runMypy, runPyright, analyzePythonDeps, pythonAudit, traceFastAPIDepends, analyzeAsyncCorrectness, getPydanticModels, reviewDiff, auditScan, indexStatus, auditAgentConfig, testImpactAnalysis, dependencyAudit, migrationLint, planTurn, formatPlanTurnResult, astroMigrationCheck, analyzePrismaSchema, findPerfHotspots, fanInFanOut, coChangeAnalysis, architectureSummary, nestAudit, explainQuery, } from "./register-tool-loaders.js";
80
9
  import { formatSnapshot, getContext, getSessionState } from "./storage/session-state.js";
81
10
  import { formatComplexityCompact, formatComplexityCounts, formatClonesCompact, formatClonesCounts, formatHotspotsCompact, formatHotspotsCounts, formatTraceRouteCompact, formatTraceRouteCounts } from "./formatters-shortening.js";
82
11
  import { formatSearchSymbols, formatFileTree, formatFileOutline, formatSearchPatterns, formatDeadCode, formatComplexity, formatClones, formatHotspots, formatRepoOutline, formatSuggestQueries, formatSecrets, formatConversations, formatRoles, formatAssembleContext, formatCommunities, formatCallTree, formatTraceRoute, formatKnowledgeMap, formatImpactAnalysis, formatDiffOutline, formatChangedSymbols, formatReviewDiff, formatPerfHotspots, formatFanInFanOut, formatCoChange, formatArchitectureSummary, formatNextjsRouteMap, formatNextjsMetadataAudit, formatFrameworkAudit } from "./formatters.js";
@@ -91,6 +20,35 @@ export const zNum = () => z.union([
91
20
  .transform((value) => Number(value))
92
21
  .pipe(zFiniteNumber),
93
22
  ]).optional();
23
+ function lazySchema(factory) {
24
+ let cached;
25
+ const resolve = () => {
26
+ cached ??= factory();
27
+ return cached;
28
+ };
29
+ return new Proxy({}, {
30
+ get(_target, prop) {
31
+ return resolve()[prop];
32
+ },
33
+ has(_target, prop) {
34
+ return prop in resolve();
35
+ },
36
+ ownKeys() {
37
+ return Reflect.ownKeys(resolve());
38
+ },
39
+ getOwnPropertyDescriptor(_target, prop) {
40
+ const descriptor = Object.getOwnPropertyDescriptor(resolve(), prop);
41
+ if (descriptor)
42
+ return descriptor;
43
+ return {
44
+ configurable: true,
45
+ enumerable: true,
46
+ writable: false,
47
+ value: resolve()[prop],
48
+ };
49
+ },
50
+ });
51
+ }
94
52
  // ---------------------------------------------------------------------------
95
53
  // H11 — warn when symbol tools return empty for repos with text_stub languages
96
54
  // ---------------------------------------------------------------------------
@@ -166,6 +124,44 @@ const toolHandles = new Map();
166
124
  export function getToolHandle(name) {
167
125
  return toolHandles.get(name);
168
126
  }
127
+ let registrationContext = null;
128
+ function isToolLanguageEnabled(tool, languages) {
129
+ if (!tool.requiresLanguage)
130
+ return true;
131
+ return languages[tool.requiresLanguage];
132
+ }
133
+ function registerToolDefinition(server, tool, languages) {
134
+ const existing = toolHandles.get(tool.name);
135
+ if (existing)
136
+ return existing;
137
+ const handle = server.tool(tool.name, tool.description, tool.schema, async (args) => wrapTool(tool.name, args, () => tool.handler(args))());
138
+ if (!isToolLanguageEnabled(tool, languages) && typeof handle.disable === "function") {
139
+ handle.disable();
140
+ }
141
+ toolHandles.set(tool.name, handle);
142
+ return handle;
143
+ }
144
+ function ensureToolRegistered(name) {
145
+ const existing = toolHandles.get(name);
146
+ if (existing)
147
+ return existing;
148
+ const context = registrationContext;
149
+ if (!context)
150
+ return undefined;
151
+ const tool = TOOL_DEFINITION_MAP.get(name);
152
+ if (!tool)
153
+ return undefined;
154
+ return registerToolDefinition(context.server, tool, context.languages);
155
+ }
156
+ function enableToolByName(name) {
157
+ const handle = ensureToolRegistered(name);
158
+ if (!handle)
159
+ return false;
160
+ if (typeof handle.enable === "function") {
161
+ handle.enable();
162
+ }
163
+ return true;
164
+ }
169
165
  /** Framework-specific tool bundles — auto-enabled when the framework is detected in an indexed repo */
170
166
  const FRAMEWORK_TOOL_BUNDLES = {
171
167
  nestjs: [
@@ -186,9 +182,7 @@ export function enableFrameworkToolBundle(framework) {
186
182
  return [];
187
183
  const enabled = [];
188
184
  for (const name of bundle) {
189
- const handle = toolHandles.get(name);
190
- if (handle && typeof handle.enable === "function") {
191
- handle.enable();
185
+ if (enableToolByName(name)) {
192
186
  enabled.push(name);
193
187
  }
194
188
  }
@@ -288,6 +282,8 @@ const HONO_TOOLS = [
288
282
  "detect_hono_modules",
289
283
  "find_dead_hono_routes",
290
284
  ];
285
+ const AUTO_LOAD_CACHE_TTL_MS = 5_000;
286
+ const autoLoadToolsCache = new Map();
291
287
  /**
292
288
  * Detect project type at CWD and return list of tools that should be auto-enabled.
293
289
  * Returns empty array if no framework-specific tools apply.
@@ -329,6 +325,24 @@ export async function detectAutoLoadTools(cwd) {
329
325
  }
330
326
  return toEnable;
331
327
  }
328
+ export function detectAutoLoadToolsCached(cwd) {
329
+ const now = Date.now();
330
+ const cached = autoLoadToolsCache.get(cwd);
331
+ if (cached && cached.expiresAt > now) {
332
+ return cached.value;
333
+ }
334
+ const value = detectAutoLoadTools(cwd)
335
+ .then((tools) => [...new Set(tools)])
336
+ .catch((err) => {
337
+ autoLoadToolsCache.delete(cwd);
338
+ throw err;
339
+ });
340
+ autoLoadToolsCache.set(cwd, {
341
+ expiresAt: now + AUTO_LOAD_CACHE_TTL_MS,
342
+ value,
343
+ });
344
+ return value;
345
+ }
332
346
  /**
333
347
  * Quick recursive scan for .tsx/.jsx files in common source dirs.
334
348
  * Limits depth to 3 and stops on first match to stay fast (<10ms on typical repos).
@@ -508,11 +522,11 @@ const TOOL_DEFINITIONS = [
508
522
  category: "indexing",
509
523
  searchHint: "index local folder directory project parse symbols",
510
524
  description: "Index a local folder, extracting symbols and building the search index",
511
- schema: {
525
+ schema: lazySchema(() => ({
512
526
  path: z.string().describe("Absolute path to the folder to index"),
513
527
  incremental: zBool().describe("Only re-index changed files"),
514
528
  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."),
515
- },
529
+ })),
516
530
  handler: (args) => indexFolder(args.path, {
517
531
  incremental: args.incremental,
518
532
  include_paths: args.include_paths,
@@ -523,11 +537,11 @@ const TOOL_DEFINITIONS = [
523
537
  category: "indexing",
524
538
  searchHint: "clone remote git repository index",
525
539
  description: "Clone and index a remote git repository",
526
- schema: {
540
+ schema: lazySchema(() => ({
527
541
  url: z.string().describe("Git clone URL"),
528
542
  branch: z.string().optional().describe("Branch to checkout"),
529
543
  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."),
530
- },
544
+ })),
531
545
  handler: (args) => indexRepo(args.url, {
532
546
  branch: args.branch,
533
547
  include_paths: args.include_paths,
@@ -539,10 +553,10 @@ const TOOL_DEFINITIONS = [
539
553
  searchHint: "list indexed repositories repos available",
540
554
  outputSchema: OutputSchemas.repoList,
541
555
  description: "List indexed repos. Only needed for multi-repo discovery — single-repo tools auto-resolve from CWD. Set compact=false for full metadata.",
542
- schema: {
556
+ schema: lazySchema(() => ({
543
557
  compact: zBool().describe("true=names only (default), false=full metadata"),
544
558
  name_contains: z.string().optional().describe("Filter repos by name substring (case-insensitive). E.g. 'tgm' matches 'local/tgm-panel'"),
545
- },
559
+ })),
546
560
  handler: (args) => {
547
561
  const opts = {
548
562
  compact: args.compact ?? true,
@@ -557,9 +571,9 @@ const TOOL_DEFINITIONS = [
557
571
  category: "indexing",
558
572
  searchHint: "clear cache invalidate re-index refresh",
559
573
  description: "Clear the index cache for a repository, forcing full re-index on next use",
560
- schema: {
574
+ schema: lazySchema(() => ({
561
575
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
562
- },
576
+ })),
563
577
  handler: (args) => invalidateCache(args.repo),
564
578
  },
565
579
  {
@@ -567,9 +581,9 @@ const TOOL_DEFINITIONS = [
567
581
  category: "indexing",
568
582
  searchHint: "re-index single file update incremental",
569
583
  description: "Re-index a single file after editing. Auto-finds repo, skips if unchanged.",
570
- schema: {
584
+ schema: lazySchema(() => ({
571
585
  path: z.string().describe("Absolute path to the file to re-index"),
572
- },
586
+ })),
573
587
  handler: (args) => indexFile(args.path),
574
588
  },
575
589
  // --- Search ---
@@ -579,7 +593,7 @@ const TOOL_DEFINITIONS = [
579
593
  searchHint: "search find symbols functions classes types methods by name signature",
580
594
  outputSchema: OutputSchemas.searchResults,
581
595
  description: "Search symbols by name/signature. Supports kind, file, and decorator filters. detail_level: compact (~15 tok), standard (default), full.",
582
- schema: {
596
+ schema: lazySchema(() => ({
583
597
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
584
598
  query: z.string().describe("Search query string"),
585
599
  kind: z.string().optional().describe("Filter by symbol kind (function, class, etc.)"),
@@ -591,7 +605,7 @@ const TOOL_DEFINITIONS = [
591
605
  detail_level: z.enum(["compact", "standard", "full"]).optional().describe("compact (~15 tok), standard (default), full (all source)"),
592
606
  token_budget: zNum().describe("Max tokens for results — greedily packs results until budget exhausted. Overrides top_k."),
593
607
  rerank: zBool().describe("Rerank results using cross-encoder model for improved relevance (requires @huggingface/transformers)"),
594
- },
608
+ })),
595
609
  handler: async (args) => {
596
610
  const results = await searchSymbols(args.repo, args.query, {
597
611
  kind: args.kind,
@@ -614,13 +628,13 @@ const TOOL_DEFINITIONS = [
614
628
  category: "search",
615
629
  searchHint: "AST tree-sitter query structural pattern matching code shape jsx react",
616
630
  description: "Search AST patterns via tree-sitter S-expressions. Finds code by structural shape. React examples (language='tsx'): `(jsx_element open_tag: (jsx_opening_element name: (identifier) @tag))` finds all JSX component usage; `(call_expression function: (identifier) @fn (#match? @fn \"^use[A-Z]\"))` finds all hook calls.",
617
- schema: {
631
+ schema: lazySchema(() => ({
618
632
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
619
633
  query: z.string().describe("Tree-sitter query in S-expression syntax. For JSX/React use language='tsx'."),
620
634
  language: z.string().describe("Tree-sitter grammar: typescript, tsx, javascript, python, go, rust, java, ruby, php"),
621
635
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
622
636
  max_matches: zNum().describe("Maximum matches to return (default: 50)"),
623
- },
637
+ })),
624
638
  handler: async (args) => {
625
639
  const { astQuery } = await import("./tools/ast-query-tools.js");
626
640
  return astQuery(args.repo, args.query, {
@@ -635,14 +649,14 @@ const TOOL_DEFINITIONS = [
635
649
  category: "search",
636
650
  searchHint: "semantic meaning intent concept embedding vector natural language",
637
651
  description: "Search code by meaning using embeddings. For intent-based queries: 'error handling', 'auth flow'. Requires indexed embeddings.",
638
- schema: {
652
+ schema: lazySchema(() => ({
639
653
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
640
654
  query: z.string().describe("Natural language query describing what you're looking for"),
641
655
  top_k: zNum().describe("Number of results (default: 10)"),
642
656
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
643
657
  exclude_tests: zBool().describe("Exclude test files from results"),
644
658
  rerank: zBool().describe("Re-rank results with cross-encoder for better precision"),
645
- },
659
+ })),
646
660
  handler: async (args) => {
647
661
  const opts = {};
648
662
  if (args.top_k != null)
@@ -661,7 +675,7 @@ const TOOL_DEFINITIONS = [
661
675
  category: "search",
662
676
  searchHint: "full-text search grep regex keyword content files",
663
677
  description: "Full-text search across all files. For conceptual queries use semantic_search.",
664
- schema: {
678
+ schema: lazySchema(() => ({
665
679
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
666
680
  query: z.string().describe("Search query or regex pattern"),
667
681
  regex: zBool().describe("Treat query as a regex pattern"),
@@ -671,7 +685,7 @@ const TOOL_DEFINITIONS = [
671
685
  group_by_file: zBool().describe("Group by file: {file, count, lines[], first_match}. ~80% less output."),
672
686
  auto_group: zBool().describe("Auto group_by_file when >50 matches."),
673
687
  ranked: z.boolean().optional().describe("Classify hits by containing symbol and rank by centrality"),
674
- },
688
+ })),
675
689
  handler: (args) => searchText(args.repo, args.query, {
676
690
  regex: args.regex,
677
691
  context_lines: args.context_lines,
@@ -689,14 +703,14 @@ const TOOL_DEFINITIONS = [
689
703
  searchHint: "file tree directory structure listing files symbols",
690
704
  outputSchema: OutputSchemas.fileTree,
691
705
  description: "File tree with symbol counts. compact=true for flat list (10-50x less output). Cached 5min.",
692
- schema: {
706
+ schema: lazySchema(() => ({
693
707
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
694
708
  path_prefix: z.string().optional().describe("Filter to a subtree by path prefix"),
695
709
  name_pattern: z.string().optional().describe("Glob pattern to filter file names"),
696
710
  depth: zNum().describe("Maximum directory depth to traverse"),
697
711
  compact: zBool().describe("Return flat list of {path, symbols} instead of nested tree (much less output)"),
698
712
  min_symbols: zNum().describe("Only include files with at least this many symbols"),
699
- },
713
+ })),
700
714
  handler: async (args) => {
701
715
  const result = await getFileTree(args.repo, {
702
716
  path_prefix: args.path_prefix,
@@ -714,10 +728,10 @@ const TOOL_DEFINITIONS = [
714
728
  searchHint: "file outline symbols functions classes exports single file",
715
729
  outputSchema: OutputSchemas.fileOutline,
716
730
  description: "Get the symbol outline of a single file (functions, classes, exports)",
717
- schema: {
731
+ schema: lazySchema(() => ({
718
732
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
719
733
  file_path: z.string().describe("Relative file path within the repository"),
720
- },
734
+ })),
721
735
  handler: async (args) => {
722
736
  const result = await getFileOutline(args.repo, args.file_path);
723
737
  const output = formatFileOutline(result);
@@ -731,9 +745,9 @@ const TOOL_DEFINITIONS = [
731
745
  category: "outline",
732
746
  searchHint: "repository outline overview directory structure high-level",
733
747
  description: "Get a high-level outline of the entire repository grouped by directory",
734
- schema: {
748
+ schema: lazySchema(() => ({
735
749
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
736
- },
750
+ })),
737
751
  handler: async (args) => {
738
752
  const result = await getRepoOutline(args.repo);
739
753
  return formatRepoOutline(result);
@@ -744,9 +758,9 @@ const TOOL_DEFINITIONS = [
744
758
  category: "outline",
745
759
  searchHint: "suggest queries explore unfamiliar repo onboarding first call",
746
760
  description: "Suggest queries for exploring a new repo. Returns top files, kind distribution, examples.",
747
- schema: {
761
+ schema: lazySchema(() => ({
748
762
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
749
- },
763
+ })),
750
764
  handler: async (args) => {
751
765
  const result = await suggestQueries(args.repo);
752
766
  return formatSuggestQueries(result);
@@ -759,11 +773,11 @@ const TOOL_DEFINITIONS = [
759
773
  searchHint: "get retrieve single symbol source code by ID",
760
774
  outputSchema: OutputSchemas.symbol,
761
775
  description: "Get symbol by ID with source. Auto-prefetches children for classes. For batch: get_symbols. For context: get_context_bundle.",
762
- schema: {
776
+ schema: lazySchema(() => ({
763
777
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
764
778
  symbol_id: z.string().describe("Unique symbol identifier"),
765
779
  include_related: zBool().describe("Include children/related symbols (default: true)"),
766
- },
780
+ })),
767
781
  handler: async (args) => {
768
782
  const opts = {};
769
783
  if (args.include_related != null)
@@ -773,7 +787,7 @@ const TOOL_DEFINITIONS = [
773
787
  const hint = await checkTextStubHint(args.repo, "get_symbol", true);
774
788
  return hint ?? null;
775
789
  }
776
- let text = formatSymbolCompact(result.symbol);
790
+ let text = await formatSymbolCompact(result.symbol);
777
791
  if (result.related && result.related.length > 0) {
778
792
  text += "\n\n--- children ---\n" + result.related.map((s) => `${s.kind} ${s.name}${s.signature ? s.signature : ""} [${s.file}:${s.start_line}]`).join("\n");
779
793
  }
@@ -785,16 +799,16 @@ const TOOL_DEFINITIONS = [
785
799
  category: "symbols",
786
800
  searchHint: "batch get multiple symbols by IDs",
787
801
  description: "Retrieve multiple symbols by ID in a single batch call",
788
- schema: {
802
+ schema: lazySchema(() => ({
789
803
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
790
804
  symbol_ids: z.union([
791
805
  z.array(z.string()),
792
806
  z.string().transform((s) => JSON.parse(s)),
793
807
  ]).describe("Array of symbol identifiers. Can be passed as JSON string."),
794
- },
808
+ })),
795
809
  handler: async (args) => {
796
810
  const syms = await getSymbols(args.repo, args.symbol_ids);
797
- const output = formatSymbolsCompact(syms);
811
+ const output = await formatSymbolsCompact(syms);
798
812
  const hint = await checkTextStubHint(args.repo, "get_symbols", syms.length === 0);
799
813
  return hint ? hint + output : output;
800
814
  },
@@ -804,18 +818,18 @@ const TOOL_DEFINITIONS = [
804
818
  category: "symbols",
805
819
  searchHint: "find symbol by name show source code references",
806
820
  description: "Find a symbol by name and show its source, optionally including references",
807
- schema: {
821
+ schema: lazySchema(() => ({
808
822
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
809
823
  query: z.string().describe("Symbol name or query to search for"),
810
824
  include_refs: zBool().describe("Include locations that reference this symbol"),
811
- },
825
+ })),
812
826
  handler: async (args) => {
813
827
  const result = await findAndShow(args.repo, args.query, args.include_refs);
814
828
  if (!result)
815
829
  return null;
816
- let text = formatSymbolCompact(result.symbol);
830
+ let text = await formatSymbolCompact(result.symbol);
817
831
  if (result.references) {
818
- text += `\n\n--- references ---\n${formatRefsCompact(result.references)}`;
832
+ text += `\n\n--- references ---\n${await formatRefsCompact(result.references)}`;
819
833
  }
820
834
  return text;
821
835
  },
@@ -825,10 +839,10 @@ const TOOL_DEFINITIONS = [
825
839
  category: "symbols",
826
840
  searchHint: "context bundle symbol imports siblings callers one call",
827
841
  description: "Symbol + imports + siblings in one call. Saves 2-3 round-trips.",
828
- schema: {
842
+ schema: lazySchema(() => ({
829
843
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
830
844
  symbol_name: z.string().describe("Symbol name to find"),
831
- },
845
+ })),
832
846
  handler: async (args) => {
833
847
  const bundle = await getContextBundle(args.repo, args.symbol_name);
834
848
  if (!bundle)
@@ -843,20 +857,20 @@ const TOOL_DEFINITIONS = [
843
857
  searchHint: "find references usages callers who uses symbol",
844
858
  outputSchema: OutputSchemas.references,
845
859
  description: "Find all references to a symbol. Pass symbol_names array for batch search.",
846
- schema: {
860
+ schema: lazySchema(() => ({
847
861
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
848
862
  symbol_name: z.string().optional().describe("Name of the symbol to find references for"),
849
863
  symbol_names: z.union([z.array(z.string()), z.string().transform((s) => JSON.parse(s))]).optional()
850
864
  .describe("Array of symbol names for batch search (reads each file once). Can be JSON string."),
851
865
  file_pattern: z.string().optional().describe("Glob pattern to filter files"),
852
- },
866
+ })),
853
867
  handler: async (args) => {
854
868
  const names = args.symbol_names;
855
869
  if (names && names.length > 0) {
856
870
  return findReferencesBatch(args.repo, names, args.file_pattern);
857
871
  }
858
872
  const refs = await findReferences(args.repo, args.symbol_name, args.file_pattern);
859
- const output = formatRefsCompact(refs);
873
+ const output = await formatRefsCompact(refs);
860
874
  const hint = await checkTextStubHint(args.repo, "find_references", refs.length === 0);
861
875
  return hint ? hint + output : output;
862
876
  },
@@ -867,7 +881,7 @@ const TOOL_DEFINITIONS = [
867
881
  searchHint: "trace call chain callers callees dependency graph mermaid react hooks",
868
882
  outputSchema: OutputSchemas.callTree,
869
883
  description: "Trace call chain: callers or callees. output_format='mermaid' for diagram. filter_react_hooks=true skips useState/useEffect etc. for cleaner React graphs.",
870
- schema: {
884
+ schema: lazySchema(() => ({
871
885
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
872
886
  symbol_name: z.string().describe("Name of the symbol to trace"),
873
887
  direction: z.enum(["callers", "callees"]).describe("Trace direction"),
@@ -876,7 +890,7 @@ const TOOL_DEFINITIONS = [
876
890
  include_tests: zBool().describe("Include test files in trace results (default: false)"),
877
891
  output_format: z.enum(["json", "mermaid"]).optional().describe("Output format: 'json' (default) or 'mermaid' (flowchart diagram)"),
878
892
  filter_react_hooks: zBool().describe("Skip edges to React stdlib hooks (useState, useEffect, etc.) to reduce call graph noise in React codebases (default: false)"),
879
- },
893
+ })),
880
894
  handler: async (args) => {
881
895
  const result = await traceCallChain(args.repo, args.symbol_name, args.direction, {
882
896
  depth: args.depth,
@@ -897,13 +911,13 @@ const TOOL_DEFINITIONS = [
897
911
  searchHint: "impact analysis blast radius git changes affected symbols",
898
912
  outputSchema: OutputSchemas.impactAnalysis,
899
913
  description: "Blast radius of git changes — affected symbols and files.",
900
- schema: {
914
+ schema: lazySchema(() => ({
901
915
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
902
916
  since: z.string().describe("Git ref to compare from (e.g. HEAD~3, commit SHA, branch)"),
903
917
  depth: zNum().describe("Depth of dependency traversal"),
904
918
  until: z.string().optional().describe("Git ref to compare to (defaults to HEAD)"),
905
919
  include_source: zBool().describe("Include full source code of affected symbols (default: false)"),
906
- },
920
+ })),
907
921
  handler: async (args) => {
908
922
  const result = await impactAnalysis(args.repo, args.since, {
909
923
  depth: args.depth,
@@ -918,14 +932,14 @@ const TOOL_DEFINITIONS = [
918
932
  category: "graph",
919
933
  searchHint: "react component tree composition render jsx parent child hierarchy",
920
934
  description: "Trace React component composition tree from a root component. Shows which components render which via JSX. React equivalent of trace_call_chain. output_format='mermaid' for diagram.",
921
- schema: {
935
+ schema: lazySchema(() => ({
922
936
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
923
937
  component_name: z.string().describe("Root component name (must have kind 'component' in index)"),
924
938
  depth: zNum().describe("Maximum depth of composition tree (default: 3)"),
925
939
  include_source: zBool().describe("Include full source of each component (default: false)"),
926
940
  include_tests: zBool().describe("Include test files (default: false)"),
927
941
  output_format: z.enum(["json", "mermaid"]).optional().describe("Output format: 'json' (default) or 'mermaid'"),
928
- },
942
+ })),
929
943
  handler: async (args) => {
930
944
  const result = await traceComponentTree(args.repo, args.component_name, {
931
945
  depth: args.depth,
@@ -941,13 +955,13 @@ const TOOL_DEFINITIONS = [
941
955
  category: "analysis",
942
956
  searchHint: "react hooks analyze inventory rule of hooks violations usestate useeffect custom",
943
957
  description: "Analyze React hooks: inventory per component, Rule of Hooks violations (hook inside if/loop, hook after early return), custom hook composition, codebase-wide hook usage summary.",
944
- schema: {
958
+ schema: lazySchema(() => ({
945
959
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
946
960
  component_name: z.string().optional().describe("Filter to single component/hook (default: all)"),
947
961
  file_pattern: z.string().optional().describe("Filter by file path substring"),
948
962
  include_tests: zBool().describe("Include test files (default: false)"),
949
963
  max_entries: zNum().describe("Max entries to return (default: 100)"),
950
- },
964
+ })),
951
965
  handler: async (args) => {
952
966
  const result = await analyzeHooks(args.repo, {
953
967
  component_name: args.component_name,
@@ -963,13 +977,13 @@ const TOOL_DEFINITIONS = [
963
977
  category: "analysis",
964
978
  searchHint: "react render performance inline props memo useCallback useMemo re-render risk optimization",
965
979
  description: "Static re-render risk analysis for React components. Detects inline object/array/function props in JSX (new reference every render), unstable default values (= [] or = {}), and components missing React.memo that render children. Returns per-component risk level (low/medium/high) with actionable suggestions.",
966
- schema: {
980
+ schema: lazySchema(() => ({
967
981
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
968
982
  component_name: z.string().optional().describe("Filter to single component (default: all)"),
969
983
  file_pattern: z.string().optional().describe("Filter by file path substring"),
970
984
  include_tests: zBool().describe("Include test files (default: false)"),
971
985
  max_entries: zNum().describe("Max entries to return (default: 100)"),
972
- },
986
+ })),
973
987
  handler: async (args) => {
974
988
  const result = await analyzeRenders(args.repo, {
975
989
  component_name: args.component_name,
@@ -985,14 +999,14 @@ const TOOL_DEFINITIONS = [
985
999
  category: "analysis",
986
1000
  searchHint: "react context createContext provider useContext consumer re-render propagation",
987
1001
  description: "Map React context flows: createContext → Provider → useContext consumers. Shows which components consume each context and which provide values. Helps identify unnecessary re-renders from context value changes.",
988
- schema: {
1002
+ schema: lazySchema(() => ({
989
1003
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
990
- },
1004
+ })),
991
1005
  handler: async (args) => {
992
1006
  const index = await getCodeIndex(args.repo);
993
1007
  if (!index)
994
1008
  throw new Error(`Repository not found: ${args.repo}`);
995
- const result = buildContextGraph(index.symbols);
1009
+ const result = await buildContextGraph(index.symbols);
996
1010
  return JSON.stringify(result, null, 2);
997
1011
  },
998
1012
  },
@@ -1001,11 +1015,11 @@ const TOOL_DEFINITIONS = [
1001
1015
  category: "analysis",
1002
1016
  searchHint: "react compiler forget memoization bailout readiness migration adoption auto-memo",
1003
1017
  description: "Audit React Compiler (v1.0) adoption readiness. Scans all components for patterns that cause silent bailout (side effects in render, ref reads, prop/state mutation, try/catch). Returns readiness score (0-100), prioritized fix list, and count of redundant manual memoization safe to remove post-adoption. No competitor offers codebase-wide compiler readiness analysis.",
1004
- schema: {
1018
+ schema: lazySchema(() => ({
1005
1019
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1006
1020
  file_pattern: z.string().optional().describe("Filter by file path substring"),
1007
1021
  include_tests: zBool().describe("Include test files (default: false)"),
1008
- },
1022
+ })),
1009
1023
  handler: async (args) => {
1010
1024
  const result = await auditCompilerReadiness(args.repo, {
1011
1025
  file_pattern: args.file_pattern,
@@ -1019,9 +1033,9 @@ const TOOL_DEFINITIONS = [
1019
1033
  category: "analysis",
1020
1034
  searchHint: "react onboarding day-1 overview stack inventory components hooks critical issues",
1021
1035
  description: "Day-1 onboarding composite for React projects. Single call returns: component/hook inventory, stack detection (state mgmt, routing, UI lib, form lib, build tool), critical pattern scan (XSS, Rule of Hooks, memory leaks), top hook usage, and suggested next queries. Replaces 5-6 manual tool calls. First tool to run on an unfamiliar React codebase.",
1022
- schema: {
1036
+ schema: lazySchema(() => ({
1023
1037
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1024
- },
1038
+ })),
1025
1039
  handler: async (args) => {
1026
1040
  const result = await reactQuickstart(args.repo);
1027
1041
  return JSON.stringify(result, null, 2);
@@ -1032,11 +1046,11 @@ const TOOL_DEFINITIONS = [
1032
1046
  category: "graph",
1033
1047
  searchHint: "trace HTTP route handler API endpoint service database NestJS Express Next.js",
1034
1048
  description: "Trace HTTP route → handler → service → DB. NestJS, Next.js, Express.",
1035
- schema: {
1049
+ schema: lazySchema(() => ({
1036
1050
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1037
1051
  path: z.string().describe("URL path to trace (e.g. '/api/users', '/api/projects/:id')"),
1038
1052
  output_format: z.enum(["json", "mermaid"]).optional().describe("Output format: 'json' (default) or 'mermaid' (sequence diagram)"),
1039
- },
1053
+ })),
1040
1054
  handler: async (args) => {
1041
1055
  const result = await traceRoute(args.repo, args.path, args.output_format);
1042
1056
  return formatTraceRoute(result);
@@ -1048,13 +1062,13 @@ const TOOL_DEFINITIONS = [
1048
1062
  searchHint: "go to definition jump navigate LSP language server",
1049
1063
  outputSchema: OutputSchemas.definition,
1050
1064
  description: "Go to the definition of a symbol. Uses LSP when available for type-safe precision, falls back to index search.",
1051
- schema: {
1065
+ schema: lazySchema(() => ({
1052
1066
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1053
1067
  symbol_name: z.string().describe("Symbol name to find definition of"),
1054
1068
  file_path: z.string().optional().describe("File containing the symbol reference (for LSP precision)"),
1055
1069
  line: zNum().describe("0-based line number of the reference"),
1056
1070
  character: zNum().describe("0-based column of the reference"),
1057
- },
1071
+ })),
1058
1072
  handler: async (args) => {
1059
1073
  const result = await goToDefinition(args.repo, args.symbol_name, args.file_path, args.line, args.character);
1060
1074
  if (!result)
@@ -1069,13 +1083,13 @@ const TOOL_DEFINITIONS = [
1069
1083
  searchHint: "type information hover documentation return type parameters LSP",
1070
1084
  outputSchema: OutputSchemas.typeInfo,
1071
1085
  description: "Get type info via LSP hover (return type, params, docs). Hint if LSP unavailable.",
1072
- schema: {
1086
+ schema: lazySchema(() => ({
1073
1087
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1074
1088
  symbol_name: z.string().describe("Symbol name to get type info for"),
1075
1089
  file_path: z.string().optional().describe("File containing the symbol"),
1076
1090
  line: zNum().describe("0-based line number"),
1077
1091
  character: zNum().describe("0-based column"),
1078
- },
1092
+ })),
1079
1093
  handler: (args) => getTypeInfo(args.repo, args.symbol_name, args.file_path, args.line, args.character),
1080
1094
  },
1081
1095
  {
@@ -1084,14 +1098,14 @@ const TOOL_DEFINITIONS = [
1084
1098
  searchHint: "rename symbol refactor LSP type-safe all files",
1085
1099
  outputSchema: OutputSchemas.renameResult,
1086
1100
  description: "Rename symbol across all files via LSP. Type-safe, updates imports/refs.",
1087
- schema: {
1101
+ schema: lazySchema(() => ({
1088
1102
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1089
1103
  symbol_name: z.string().describe("Current name of the symbol to rename"),
1090
1104
  new_name: z.string().describe("New name for the symbol"),
1091
1105
  file_path: z.string().optional().describe("File containing the symbol"),
1092
1106
  line: zNum().describe("0-based line number"),
1093
1107
  character: zNum().describe("0-based column"),
1094
- },
1108
+ })),
1095
1109
  handler: (args) => renameSymbol(args.repo, args.symbol_name, args.new_name, args.file_path, args.line, args.character),
1096
1110
  },
1097
1111
  {
@@ -1100,13 +1114,13 @@ const TOOL_DEFINITIONS = [
1100
1114
  searchHint: "call hierarchy incoming outgoing calls who calls what calls LSP callers callees",
1101
1115
  outputSchema: OutputSchemas.callHierarchy,
1102
1116
  description: "LSP call hierarchy: incoming + outgoing calls. Complements trace_call_chain.",
1103
- schema: {
1117
+ schema: lazySchema(() => ({
1104
1118
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1105
1119
  symbol_name: z.string().describe("Symbol name to get call hierarchy for"),
1106
1120
  file_path: z.string().optional().describe("File containing the symbol (for LSP precision)"),
1107
1121
  line: zNum().describe("0-based line number"),
1108
1122
  character: zNum().describe("0-based column"),
1109
- },
1123
+ })),
1110
1124
  handler: async (args) => {
1111
1125
  const result = await getCallHierarchy(args.repo, args.symbol_name, args.file_path, args.line, args.character);
1112
1126
  if (result.via === "unavailable") {
@@ -1138,12 +1152,12 @@ const TOOL_DEFINITIONS = [
1138
1152
  category: "architecture",
1139
1153
  searchHint: "community detection clusters modules Louvain import graph boundaries",
1140
1154
  description: "Louvain community detection on import graph. Discovers module boundaries.",
1141
- schema: {
1155
+ schema: lazySchema(() => ({
1142
1156
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1143
1157
  focus: z.string().optional().describe("Path substring to filter files (e.g. 'src/lib')"),
1144
1158
  resolution: zNum().describe("Louvain resolution: higher = more smaller communities, lower = fewer larger (default: 1.0)"),
1145
1159
  output_format: z.enum(["json", "mermaid"]).optional().describe("Output format: 'json' (default) or 'mermaid' (graph diagram)"),
1146
- },
1160
+ })),
1147
1161
  handler: async (args) => {
1148
1162
  const result = await detectCommunities(args.repo, args.focus, args.resolution, args.output_format);
1149
1163
  return formatCommunities(result);
@@ -1154,11 +1168,11 @@ const TOOL_DEFINITIONS = [
1154
1168
  category: "architecture",
1155
1169
  searchHint: "circular dependency cycle import loop detection",
1156
1170
  description: "Detect circular dependencies in the import graph via DFS. Returns file-level cycles.",
1157
- schema: {
1171
+ schema: lazySchema(() => ({
1158
1172
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1159
1173
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
1160
1174
  max_cycles: zNum().describe("Maximum cycles to report (default: 50)"),
1161
- },
1175
+ })),
1162
1176
  handler: async (args) => {
1163
1177
  const { findCircularDeps } = await import("./tools/graph-tools.js");
1164
1178
  const opts = {};
@@ -1182,7 +1196,7 @@ const TOOL_DEFINITIONS = [
1182
1196
  category: "architecture",
1183
1197
  searchHint: "boundary rules architecture enforcement imports CI gate hexagonal onion",
1184
1198
  description: "Check architecture boundary rules against imports. Path substring matching.",
1185
- schema: {
1199
+ schema: lazySchema(() => ({
1186
1200
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1187
1201
  rules: z.union([
1188
1202
  z.array(z.object({
@@ -1193,7 +1207,7 @@ const TOOL_DEFINITIONS = [
1193
1207
  z.string().transform((s) => JSON.parse(s)),
1194
1208
  ]).describe("Array of boundary rules to check. JSON string OK."),
1195
1209
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
1196
- },
1210
+ })),
1197
1211
  handler: async (args) => {
1198
1212
  const { checkBoundaries } = await import("./tools/boundary-tools.js");
1199
1213
  return checkBoundaries(args.repo, args.rules, { file_pattern: args.file_pattern });
@@ -1204,12 +1218,12 @@ const TOOL_DEFINITIONS = [
1204
1218
  category: "architecture",
1205
1219
  searchHint: "classify roles entry core utility dead leaf symbol architecture",
1206
1220
  description: "Classify symbol roles (entry/core/utility/dead/leaf) by call graph connectivity.",
1207
- schema: {
1221
+ schema: lazySchema(() => ({
1208
1222
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1209
1223
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
1210
1224
  include_tests: zBool().describe("Include test files (default: false)"),
1211
1225
  top_n: zNum().describe("Maximum number of symbols to return (default: 100)"),
1212
- },
1226
+ })),
1213
1227
  handler: async (args) => {
1214
1228
  const { classifySymbolRoles } = await import("./tools/graph-tools.js");
1215
1229
  const result = await classifySymbolRoles(args.repo, {
@@ -1226,13 +1240,13 @@ const TOOL_DEFINITIONS = [
1226
1240
  category: "context",
1227
1241
  searchHint: "assemble context token budget L0 L1 L2 L3 source signatures summaries",
1228
1242
  description: "Assemble code context within token budget. L0=source, L1=signatures, L2=files, L3=dirs.",
1229
- schema: {
1243
+ schema: lazySchema(() => ({
1230
1244
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1231
1245
  query: z.string().describe("Natural language query describing what context is needed"),
1232
1246
  token_budget: zNum().describe("Maximum tokens for the assembled context"),
1233
1247
  level: z.enum(["L0", "L1", "L2", "L3"]).optional().describe("L0=source (default), L1=signatures, L2=files, L3=dirs"),
1234
1248
  rerank: zBool().describe("Rerank results using cross-encoder model for improved relevance (requires @huggingface/transformers)"),
1235
- },
1249
+ })),
1236
1250
  handler: async (args) => {
1237
1251
  const result = await assembleContext(args.repo, args.query, args.token_budget, args.level, args.rerank);
1238
1252
  return formatAssembleContext(result);
@@ -1243,12 +1257,12 @@ const TOOL_DEFINITIONS = [
1243
1257
  category: "context",
1244
1258
  searchHint: "knowledge map module dependency graph architecture overview mermaid",
1245
1259
  description: "Get the module dependency map showing how files and directories relate",
1246
- schema: {
1260
+ schema: lazySchema(() => ({
1247
1261
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1248
1262
  focus: z.string().optional().describe("Focus on a specific module or directory"),
1249
1263
  depth: zNum().describe("Maximum depth of the dependency graph"),
1250
1264
  output_format: z.enum(["json", "mermaid"]).optional().describe("Output format: 'json' (default) or 'mermaid' (dependency diagram)"),
1251
- },
1265
+ })),
1252
1266
  handler: async (args) => {
1253
1267
  const result = await getKnowledgeMap(args.repo, args.focus, args.depth, args.output_format);
1254
1268
  return formatKnowledgeMap(result);
@@ -1260,11 +1274,11 @@ const TOOL_DEFINITIONS = [
1260
1274
  category: "diff",
1261
1275
  searchHint: "diff outline structural changes git refs compare",
1262
1276
  description: "Get a structural outline of what changed between two git refs",
1263
- schema: {
1277
+ schema: lazySchema(() => ({
1264
1278
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1265
1279
  since: z.string().describe("Git ref to compare from"),
1266
1280
  until: z.string().optional().describe("Git ref to compare to (defaults to HEAD)"),
1267
- },
1281
+ })),
1268
1282
  handler: async (args) => {
1269
1283
  const result = await diffOutline(args.repo, args.since, args.until);
1270
1284
  return formatDiffOutline(result);
@@ -1275,12 +1289,12 @@ const TOOL_DEFINITIONS = [
1275
1289
  category: "diff",
1276
1290
  searchHint: "changed symbols added modified removed git diff",
1277
1291
  description: "List symbols that were added, modified, or removed between two git refs",
1278
- schema: {
1292
+ schema: lazySchema(() => ({
1279
1293
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1280
1294
  since: z.string().describe("Git ref to compare from"),
1281
1295
  until: z.string().optional().describe("Git ref to compare to (defaults to HEAD)"),
1282
1296
  include_diff: zBool().describe("Include unified diff per changed file (truncated to 500 chars)"),
1283
- },
1297
+ })),
1284
1298
  handler: async (args) => {
1285
1299
  const opts = {};
1286
1300
  if (args.include_diff === true)
@@ -1295,10 +1309,10 @@ const TOOL_DEFINITIONS = [
1295
1309
  category: "reporting",
1296
1310
  searchHint: "generate CLAUDE.md project summary documentation",
1297
1311
  description: "Generate a CLAUDE.md project summary file from the repository index",
1298
- schema: {
1312
+ schema: lazySchema(() => ({
1299
1313
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1300
1314
  output_path: z.string().optional().describe("Custom output file path"),
1301
- },
1315
+ })),
1302
1316
  handler: (args) => generateClaudeMd(args.repo, args.output_path),
1303
1317
  },
1304
1318
  // --- Batch retrieval ---
@@ -1308,7 +1322,7 @@ const TOOL_DEFINITIONS = [
1308
1322
  searchHint: "batch retrieval multi-query semantic hybrid token budget",
1309
1323
  outputSchema: OutputSchemas.batchResults,
1310
1324
  description: "Batch multi-query retrieval with shared token budget. Supports symbols/text/semantic/hybrid.",
1311
- schema: {
1325
+ schema: lazySchema(() => ({
1312
1326
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1313
1327
  queries: z
1314
1328
  .union([
@@ -1317,7 +1331,7 @@ const TOOL_DEFINITIONS = [
1317
1331
  ])
1318
1332
  .describe("Sub-queries array (symbols/text/file_tree/outline/references/call_chain/impact/context/knowledge_map). JSON string OK."),
1319
1333
  token_budget: zNum().describe("Maximum total tokens across all sub-query results"),
1320
- },
1334
+ })),
1321
1335
  handler: async (args) => {
1322
1336
  const result = await codebaseRetrieval(args.repo, args.queries, args.token_budget);
1323
1337
  // Format as text sections instead of JSON envelope
@@ -1338,11 +1352,11 @@ const TOOL_DEFINITIONS = [
1338
1352
  searchHint: "dead code unused exports unreferenced symbols cleanup",
1339
1353
  outputSchema: OutputSchemas.deadCode,
1340
1354
  description: "Find dead code: exported symbols with zero external references.",
1341
- schema: {
1355
+ schema: lazySchema(() => ({
1342
1356
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1343
1357
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
1344
1358
  include_tests: zBool().describe("Include test files in scan (default: false)"),
1345
- },
1359
+ })),
1346
1360
  handler: async (args) => {
1347
1361
  const result = await findDeadCode(args.repo, {
1348
1362
  file_pattern: args.file_pattern,
@@ -1359,11 +1373,11 @@ const TOOL_DEFINITIONS = [
1359
1373
  category: "analysis",
1360
1374
  searchHint: "unused imports dead cleanup lint",
1361
1375
  description: "Find imported names never referenced in the file body. Complements find_dead_code.",
1362
- schema: {
1376
+ schema: lazySchema(() => ({
1363
1377
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1364
1378
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
1365
1379
  include_tests: zBool().describe("Include test files in scan (default: false)"),
1366
- },
1380
+ })),
1367
1381
  handler: async (args) => {
1368
1382
  const { findUnusedImports } = await import("./tools/symbol-tools.js");
1369
1383
  const opts = {};
@@ -1388,13 +1402,13 @@ const TOOL_DEFINITIONS = [
1388
1402
  searchHint: "complexity cyclomatic nesting refactoring functions",
1389
1403
  outputSchema: OutputSchemas.complexity,
1390
1404
  description: "Top N most complex functions by cyclomatic complexity, nesting, lines.",
1391
- schema: {
1405
+ schema: lazySchema(() => ({
1392
1406
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1393
1407
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
1394
1408
  top_n: zNum().describe("Return top N most complex functions (default: 30)"),
1395
1409
  min_complexity: zNum().describe("Minimum cyclomatic complexity to include (default: 1)"),
1396
1410
  include_tests: zBool().describe("Include test files (default: false)"),
1397
- },
1411
+ })),
1398
1412
  handler: async (args) => {
1399
1413
  const result = await analyzeComplexity(args.repo, {
1400
1414
  file_pattern: args.file_pattern,
@@ -1414,13 +1428,13 @@ const TOOL_DEFINITIONS = [
1414
1428
  searchHint: "code clones duplicates copy-paste detection similar functions",
1415
1429
  outputSchema: OutputSchemas.clones,
1416
1430
  description: "Find code clones: similar function pairs via hash bucketing + line-similarity.",
1417
- schema: {
1431
+ schema: lazySchema(() => ({
1418
1432
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1419
1433
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
1420
1434
  min_similarity: zNum().describe("Minimum similarity threshold 0-1 (default: 0.7)"),
1421
1435
  min_lines: zNum().describe("Minimum normalized lines to consider (default: 10)"),
1422
1436
  include_tests: zBool().describe("Include test files (default: false)"),
1423
- },
1437
+ })),
1424
1438
  handler: async (args) => {
1425
1439
  const result = await findClones(args.repo, {
1426
1440
  file_pattern: args.file_pattern,
@@ -1436,7 +1450,7 @@ const TOOL_DEFINITIONS = [
1436
1450
  category: "analysis",
1437
1451
  searchHint: "frequency analysis common patterns AST shape clusters",
1438
1452
  description: "Group functions by normalized AST shape. Finds emergent patterns invisible to regex.",
1439
- schema: {
1453
+ schema: lazySchema(() => ({
1440
1454
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1441
1455
  top_n: zNum().optional().describe("Number of clusters to return (default: 30)"),
1442
1456
  min_nodes: zNum().optional().describe("Minimum AST nodes in a subtree to include (default: 5)"),
@@ -1444,7 +1458,7 @@ const TOOL_DEFINITIONS = [
1444
1458
  kind: z.string().optional().describe("Filter by symbol kind, comma-separated (default: function,method)"),
1445
1459
  include_tests: zBool().describe("Include test files (default: false)"),
1446
1460
  token_budget: zNum().optional().describe("Max tokens for response"),
1447
- },
1461
+ })),
1448
1462
  handler: async (args) => frequencyAnalysis(args.repo, {
1449
1463
  top_n: args.top_n,
1450
1464
  min_nodes: args.min_nodes,
@@ -1459,12 +1473,12 @@ const TOOL_DEFINITIONS = [
1459
1473
  category: "analysis",
1460
1474
  searchHint: "hotspots git churn bug-prone change frequency complexity",
1461
1475
  description: "Git churn hotspots: change frequency × complexity. Higher score = more bug-prone.",
1462
- schema: {
1476
+ schema: lazySchema(() => ({
1463
1477
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1464
1478
  since_days: zNum().describe("Look back N days (default: 90)"),
1465
1479
  top_n: zNum().describe("Return top N hotspots (default: 30)"),
1466
1480
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
1467
- },
1481
+ })),
1468
1482
  handler: async (args) => {
1469
1483
  const result = await analyzeHotspots(args.repo, {
1470
1484
  since_days: args.since_days,
@@ -1480,13 +1494,13 @@ const TOOL_DEFINITIONS = [
1480
1494
  category: "cross-repo",
1481
1495
  searchHint: "cross-repo search symbols across all repositories monorepo microservice",
1482
1496
  description: "Search symbols across ALL indexed repositories. Useful for monorepos and microservice architectures.",
1483
- schema: {
1497
+ schema: lazySchema(() => ({
1484
1498
  query: z.string().describe("Symbol search query"),
1485
1499
  repo_pattern: z.string().optional().describe("Filter repos by name pattern (e.g. 'local/tgm')"),
1486
1500
  kind: z.string().optional().describe("Filter by symbol kind"),
1487
1501
  top_k: zNum().describe("Max results per repo (default: 10)"),
1488
1502
  include_source: zBool().describe("Include source code"),
1489
- },
1503
+ })),
1490
1504
  handler: (args) => crossRepoSearchSymbols(args.query, {
1491
1505
  repo_pattern: args.repo_pattern,
1492
1506
  kind: args.kind,
@@ -1499,11 +1513,11 @@ const TOOL_DEFINITIONS = [
1499
1513
  category: "cross-repo",
1500
1514
  searchHint: "cross-repo references symbol across all repositories",
1501
1515
  description: "Find references to a symbol across ALL indexed repositories.",
1502
- schema: {
1516
+ schema: lazySchema(() => ({
1503
1517
  symbol_name: z.string().describe("Symbol name to find references for"),
1504
1518
  repo_pattern: z.string().optional().describe("Filter repos by name pattern"),
1505
1519
  file_pattern: z.string().optional().describe("Filter files by glob pattern"),
1506
- },
1520
+ })),
1507
1521
  handler: (args) => crossRepoFindReferences(args.symbol_name, {
1508
1522
  repo_pattern: args.repo_pattern,
1509
1523
  file_pattern: args.file_pattern,
@@ -1515,13 +1529,13 @@ const TOOL_DEFINITIONS = [
1515
1529
  category: "patterns",
1516
1530
  searchHint: "search patterns anti-patterns CQ violations useEffect empty-catch console-log",
1517
1531
  description: "Search structural patterns/anti-patterns. Built-in or custom regex.",
1518
- schema: {
1532
+ schema: lazySchema(() => ({
1519
1533
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1520
1534
  pattern: z.string().describe("Built-in pattern name or custom regex"),
1521
1535
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
1522
1536
  include_tests: zBool().describe("Include test files (default: false)"),
1523
1537
  max_results: zNum().describe("Max results (default: 50)"),
1524
- },
1538
+ })),
1525
1539
  handler: async (args) => {
1526
1540
  const result = await searchPatterns(args.repo, args.pattern, {
1527
1541
  file_pattern: args.file_pattern,
@@ -1536,7 +1550,7 @@ const TOOL_DEFINITIONS = [
1536
1550
  category: "patterns",
1537
1551
  searchHint: "list available built-in patterns anti-patterns",
1538
1552
  description: "List all available built-in structural code patterns for search_patterns.",
1539
- schema: {},
1553
+ schema: lazySchema(() => ({})),
1540
1554
  handler: async () => listPatterns(),
1541
1555
  },
1542
1556
  // --- Report ---
@@ -1545,9 +1559,9 @@ const TOOL_DEFINITIONS = [
1545
1559
  category: "reporting",
1546
1560
  searchHint: "generate HTML report complexity dead code hotspots architecture browser",
1547
1561
  description: "Generate a standalone HTML report with complexity, dead code, hotspots, and architecture. Opens in any browser.",
1548
- schema: {
1562
+ schema: lazySchema(() => ({
1549
1563
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1550
- },
1564
+ })),
1551
1565
  handler: (args) => generateReport(args.repo),
1552
1566
  },
1553
1567
  // --- Conversations ---
@@ -1556,10 +1570,10 @@ const TOOL_DEFINITIONS = [
1556
1570
  category: "conversations",
1557
1571
  searchHint: "index conversations Claude Code history JSONL",
1558
1572
  description: "Index Claude Code conversation history for search. Scans JSONL files in ~/.claude/projects/ for the given project path.",
1559
- schema: {
1573
+ schema: lazySchema(() => ({
1560
1574
  project_path: z.string().optional().describe("Path to the Claude project conversations directory. Auto-detects from cwd if omitted."),
1561
1575
  quiet: zBool().describe("Suppress output (used by session-end hook)"),
1562
- },
1576
+ })),
1563
1577
  handler: async (args) => indexConversations(args.project_path),
1564
1578
  },
1565
1579
  {
@@ -1567,11 +1581,11 @@ const TOOL_DEFINITIONS = [
1567
1581
  category: "conversations",
1568
1582
  searchHint: "search conversations past sessions history BM25 semantic",
1569
1583
  description: "Search conversations in one project (BM25+semantic). For all projects: search_all_conversations.",
1570
- schema: {
1584
+ schema: lazySchema(() => ({
1571
1585
  query: z.string().describe("Search query — keywords or natural language"),
1572
1586
  project: z.string().optional().describe("Project path to search (default: current project)"),
1573
1587
  limit: zNum().optional().describe("Maximum results to return (default: 10, max: 50)"),
1574
- },
1588
+ })),
1575
1589
  handler: async (args) => {
1576
1590
  const result = await searchConversations(args.query, args.project, args.limit);
1577
1591
  return formatConversations(result);
@@ -1582,11 +1596,11 @@ const TOOL_DEFINITIONS = [
1582
1596
  category: "conversations",
1583
1597
  searchHint: "find conversations symbol discussion cross-reference code",
1584
1598
  description: "Find conversations that discussed a code symbol. Cross-refs code + history.",
1585
- schema: {
1599
+ schema: lazySchema(() => ({
1586
1600
  symbol_name: z.string().describe("Name of the code symbol to search for in conversations"),
1587
1601
  repo: z.string().describe("Code repository to resolve the symbol from (e.g., 'local/my-project')"),
1588
1602
  limit: zNum().optional().describe("Maximum conversation results (default: 5)"),
1589
- },
1603
+ })),
1590
1604
  handler: async (args) => {
1591
1605
  const result = await findConversationsForSymbol(args.symbol_name, args.repo, args.limit);
1592
1606
  return formatConversations(result);
@@ -1597,10 +1611,10 @@ const TOOL_DEFINITIONS = [
1597
1611
  category: "conversations",
1598
1612
  searchHint: "search all conversations every project cross-project",
1599
1613
  description: "Search ALL conversation projects at once, ranked by relevance.",
1600
- schema: {
1614
+ schema: lazySchema(() => ({
1601
1615
  query: z.string().describe("Search query — keywords, natural language, or concept"),
1602
1616
  limit: zNum().optional().describe("Maximum results across all projects (default: 10)"),
1603
- },
1617
+ })),
1604
1618
  handler: async (args) => {
1605
1619
  const result = await searchAllConversations(args.query, args.limit);
1606
1620
  return formatConversations(result);
@@ -1613,13 +1627,13 @@ const TOOL_DEFINITIONS = [
1613
1627
  searchHint: "scan secrets API keys tokens passwords credentials security",
1614
1628
  outputSchema: OutputSchemas.secrets,
1615
1629
  description: "Scan for hardcoded secrets (API keys, tokens, passwords). ~1,100 rules.",
1616
- schema: {
1630
+ schema: lazySchema(() => ({
1617
1631
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1618
1632
  file_pattern: z.string().optional().describe("Glob pattern to filter scanned files"),
1619
1633
  min_confidence: z.enum(["high", "medium", "low"]).optional().describe("Minimum confidence level (default: medium)"),
1620
1634
  exclude_tests: zBool().describe("Exclude test file findings (default: true)"),
1621
1635
  severity: z.enum(["critical", "high", "medium", "low"]).optional().describe("Minimum severity level"),
1622
- },
1636
+ })),
1623
1637
  handler: async (args) => {
1624
1638
  const result = await scanSecrets(args.repo, {
1625
1639
  file_pattern: args.file_pattern,
@@ -1637,11 +1651,11 @@ const TOOL_DEFINITIONS = [
1637
1651
  requiresLanguage: "kotlin",
1638
1652
  searchHint: "kotlin extension function receiver type method discovery",
1639
1653
  description: "Find all Kotlin extension functions for a given receiver type. Scans indexed symbols for signatures matching 'ReceiverType.' prefix.",
1640
- schema: {
1654
+ schema: lazySchema(() => ({
1641
1655
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1642
1656
  receiver_type: z.string().describe("Receiver type name, e.g. 'String', 'List', 'User'"),
1643
1657
  file_pattern: z.string().optional().describe("Filter by file path substring"),
1644
- },
1658
+ })),
1645
1659
  handler: async (args) => {
1646
1660
  const opts = {};
1647
1661
  if (typeof args.file_pattern === "string")
@@ -1655,10 +1669,10 @@ const TOOL_DEFINITIONS = [
1655
1669
  requiresLanguage: "kotlin",
1656
1670
  searchHint: "kotlin sealed class interface subtype when exhaustive branch missing hierarchy",
1657
1671
  description: "Analyze a Kotlin sealed class/interface: find all subtypes and check when() blocks for exhaustiveness (missing branches).",
1658
- schema: {
1672
+ schema: lazySchema(() => ({
1659
1673
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1660
1674
  sealed_class: z.string().describe("Name of the sealed class or interface to analyze"),
1661
- },
1675
+ })),
1662
1676
  handler: async (args) => {
1663
1677
  return await analyzeSealedHierarchy(args.repo, args.sealed_class);
1664
1678
  },
@@ -1668,11 +1682,11 @@ const TOOL_DEFINITIONS = [
1668
1682
  category: "analysis",
1669
1683
  searchHint: "hilt dagger DI dependency injection viewmodel inject module provides binds android kotlin graph",
1670
1684
  description: "Trace a Hilt DI dependency tree rooted at a class annotated with @HiltViewModel / @AndroidEntryPoint / @HiltAndroidApp. Returns constructor dependencies with matching @Provides/@Binds providers and their module. Unresolved deps are flagged.",
1671
- schema: {
1685
+ schema: lazySchema(() => ({
1672
1686
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1673
1687
  class_name: z.string().describe("Name of the Hilt-annotated class (e.g. 'UserViewModel')"),
1674
1688
  depth: z.number().optional().describe("Max traversal depth (default: 1)"),
1675
- },
1689
+ })),
1676
1690
  handler: async (args) => {
1677
1691
  const opts = {};
1678
1692
  if (typeof args.depth === "number")
@@ -1685,11 +1699,11 @@ const TOOL_DEFINITIONS = [
1685
1699
  category: "analysis",
1686
1700
  searchHint: "kotlin coroutine suspend dispatcher withContext runBlocking Thread.sleep blocking chain trace anti-pattern",
1687
1701
  description: "Trace the call chain of a Kotlin suspend function, emitting dispatcher transitions (withContext(Dispatchers.X)) and warnings for coroutine anti-patterns: runBlocking inside suspend, Thread.sleep, non-cancellable while(true) loops. Lexical walk — follows callee names found in the source, filtered to suspend-only functions.",
1688
- schema: {
1702
+ schema: lazySchema(() => ({
1689
1703
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1690
1704
  function_name: z.string().describe("Name of the suspend function to trace"),
1691
1705
  depth: z.number().optional().describe("Max chain depth (default: 3)"),
1692
- },
1706
+ })),
1693
1707
  handler: async (args) => {
1694
1708
  const opts = {};
1695
1709
  if (typeof args.depth === "number")
@@ -1702,9 +1716,9 @@ const TOOL_DEFINITIONS = [
1702
1716
  category: "analysis",
1703
1717
  searchHint: "kotlin multiplatform kmp expect actual source set common main android ios jvm js missing orphan",
1704
1718
  description: "Validate Kotlin Multiplatform expect/actual declarations across source sets. For each `expect` in commonMain, check every platform source set (androidMain/iosMain/jvmMain/jsMain/etc. discovered from the repo layout) for a matching `actual`. Reports fully matched pairs, expects missing on a platform, and orphan actuals with no corresponding expect.",
1705
- schema: {
1719
+ schema: lazySchema(() => ({
1706
1720
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1707
- },
1721
+ })),
1708
1722
  handler: async (args) => {
1709
1723
  return await analyzeKmpDeclarations(args.repo);
1710
1724
  },
@@ -1715,11 +1729,11 @@ const TOOL_DEFINITIONS = [
1715
1729
  category: "analysis",
1716
1730
  searchHint: "kotlin compose composable component tree hierarchy ui call graph jetpack preview",
1717
1731
  description: "Build a Jetpack Compose component hierarchy rooted at a @Composable function. Traces PascalCase calls matching indexed composables, excludes @Preview. Reports tree depth, leaf components, and total component count.",
1718
- schema: {
1732
+ schema: lazySchema(() => ({
1719
1733
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1720
1734
  root_name: z.string().describe("Name of the root @Composable function (e.g. 'HomeScreen')"),
1721
1735
  depth: z.number().optional().describe("Max tree depth (default: 10)"),
1722
- },
1736
+ })),
1723
1737
  handler: async (args) => {
1724
1738
  const opts = {};
1725
1739
  if (typeof args.depth === "number")
@@ -1732,10 +1746,10 @@ const TOOL_DEFINITIONS = [
1732
1746
  category: "analysis",
1733
1747
  searchHint: "kotlin compose recomposition unstable remember mutableStateOf performance skip lambda collection",
1734
1748
  description: "Detect recomposition hazards in @Composable functions: mutableStateOf without remember (critical), unstable collection parameters (List/Map/Set), excessive function-type params. Scans all indexed composables, skipping @Preview.",
1735
- schema: {
1749
+ schema: lazySchema(() => ({
1736
1750
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1737
1751
  file_pattern: z.string().optional().describe("Filter by file path substring"),
1738
- },
1752
+ })),
1739
1753
  handler: async (args) => {
1740
1754
  const opts = {};
1741
1755
  if (typeof args.file_pattern === "string")
@@ -1748,9 +1762,9 @@ const TOOL_DEFINITIONS = [
1748
1762
  category: "analysis",
1749
1763
  searchHint: "kotlin room database entity dao query insert update delete schema sqlite persistence android",
1750
1764
  description: "Build a Room persistence schema graph: @Entity classes (with table names, primary keys), @Dao interfaces (with @Query SQL extraction), @Database declarations (with entity refs and version). Index-only.",
1751
- schema: {
1765
+ schema: lazySchema(() => ({
1752
1766
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1753
- },
1767
+ })),
1754
1768
  handler: async (args) => {
1755
1769
  return await traceRoomSchema(args.repo);
1756
1770
  },
@@ -1760,11 +1774,11 @@ const TOOL_DEFINITIONS = [
1760
1774
  category: "analysis",
1761
1775
  searchHint: "kotlin serialization serializable json schema serialname field type api contract data class",
1762
1776
  description: "Derive JSON field schema from @Serializable data classes. Extracts field names, types, @SerialName remapping, nullable flags, and defaults.",
1763
- schema: {
1777
+ schema: lazySchema(() => ({
1764
1778
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1765
1779
  file_pattern: z.string().optional().describe("Filter by file path substring"),
1766
1780
  class_name: z.string().optional().describe("Filter to a single class by name"),
1767
- },
1781
+ })),
1768
1782
  handler: async (args) => {
1769
1783
  const opts = {};
1770
1784
  if (typeof args.file_pattern === "string")
@@ -1779,10 +1793,10 @@ const TOOL_DEFINITIONS = [
1779
1793
  category: "analysis",
1780
1794
  searchHint: "kotlin flow coroutine operator map filter collect stateIn shareIn catch chain pipeline reactive",
1781
1795
  description: "Analyze a Kotlin Flow<T> operator chain: detects 50+ operators, reports ordered list, warns about .collect without .catch and .stateIn without lifecycle scope.",
1782
- schema: {
1796
+ schema: lazySchema(() => ({
1783
1797
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1784
1798
  symbol_name: z.string().describe("Name of the function or property containing the Flow chain"),
1785
- },
1799
+ })),
1786
1800
  handler: async (args) => {
1787
1801
  return await traceFlowChain(args.repo, args.symbol_name);
1788
1802
  },
@@ -1794,11 +1808,11 @@ const TOOL_DEFINITIONS = [
1794
1808
  requiresLanguage: "python",
1795
1809
  searchHint: "python django sqlalchemy orm model relationship foreignkey manytomany entity graph mermaid",
1796
1810
  description: "Extract ORM model relationships (Django ForeignKey/M2M/O2O, SQLAlchemy relationship). JSON or mermaid erDiagram.",
1797
- schema: {
1811
+ schema: lazySchema(() => ({
1798
1812
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1799
1813
  file_pattern: z.string().optional().describe("Filter by file path substring"),
1800
1814
  output_format: z.enum(["json", "mermaid"]).optional().describe("Output as structured JSON or mermaid erDiagram"),
1801
- },
1815
+ })),
1802
1816
  handler: async (args) => {
1803
1817
  const opts = {};
1804
1818
  if (args.file_pattern != null)
@@ -1814,10 +1828,10 @@ const TOOL_DEFINITIONS = [
1814
1828
  requiresLanguage: "python",
1815
1829
  searchHint: "python pytest fixture conftest scope autouse dependency graph session function",
1816
1830
  description: "Extract pytest fixture dependency graph: conftest hierarchy, scope, autouse, fixture-to-fixture deps.",
1817
- schema: {
1831
+ schema: lazySchema(() => ({
1818
1832
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1819
1833
  file_pattern: z.string().optional().describe("Filter by file path substring"),
1820
- },
1834
+ })),
1821
1835
  handler: async (args) => {
1822
1836
  const opts = {};
1823
1837
  if (args.file_pattern != null)
@@ -1831,10 +1845,10 @@ const TOOL_DEFINITIONS = [
1831
1845
  requiresLanguage: "python",
1832
1846
  searchHint: "python django signal receiver celery task middleware management command flask fastapi event wiring",
1833
1847
  description: "Discover implicit control flow: Django signals, Celery tasks/.delay() calls, middleware, management commands, Flask init_app, FastAPI events.",
1834
- schema: {
1848
+ schema: lazySchema(() => ({
1835
1849
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1836
1850
  file_pattern: z.string().optional().describe("Filter by file path substring"),
1837
- },
1851
+ })),
1838
1852
  handler: async (args) => {
1839
1853
  const opts = {};
1840
1854
  if (args.file_pattern != null)
@@ -1848,12 +1862,12 @@ const TOOL_DEFINITIONS = [
1848
1862
  requiresLanguage: "python",
1849
1863
  searchHint: "python ruff lint check bugbear performance simplify security async unused argument",
1850
1864
  description: "Run ruff linter with symbol graph correlation. Configurable rule categories (B, PERF, SIM, UP, S, ASYNC, RET, ARG).",
1851
- schema: {
1865
+ schema: lazySchema(() => ({
1852
1866
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1853
1867
  categories: z.array(z.string()).optional().describe("Rule categories to enable (default: B,PERF,SIM,UP,S,ASYNC,RET,ARG)"),
1854
1868
  file_pattern: z.string().optional().describe("Filter by file path substring"),
1855
1869
  max_results: zFiniteNumber.optional().describe("Max findings to return (default: 100)"),
1856
- },
1870
+ })),
1857
1871
  handler: async (args) => {
1858
1872
  const opts = {};
1859
1873
  if (args.categories != null)
@@ -1871,7 +1885,7 @@ const TOOL_DEFINITIONS = [
1871
1885
  requiresLanguage: "python",
1872
1886
  searchHint: "python pyproject toml dependencies version build system entry points scripts tools ruff pytest mypy",
1873
1887
  description: "Parse pyproject.toml: name, version, Python version, build system, dependencies, optional groups, entry points, configured tools.",
1874
- schema: { repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)") },
1888
+ schema: lazySchema(() => ({ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)") })),
1875
1889
  handler: async (args) => { return await parsePyproject(args.repo); },
1876
1890
  },
1877
1891
  {
@@ -1879,13 +1893,13 @@ const TOOL_DEFINITIONS = [
1879
1893
  category: "analysis",
1880
1894
  searchHint: "python typescript nestjs resolve constant value literal alias import default parameter propagation",
1881
1895
  description: "Resolve Python or TypeScript constants and function default values through simple aliases and import chains. Returns literals or explicit unresolved reasons.",
1882
- schema: {
1896
+ schema: lazySchema(() => ({
1883
1897
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1884
1898
  symbol_name: z.string().describe("Constant, function, or method name to resolve"),
1885
1899
  file_pattern: z.string().optional().describe("Filter candidate symbols by file path substring"),
1886
1900
  language: z.enum(["python", "typescript"]).optional().describe("Force resolver language instead of auto-inference"),
1887
1901
  max_depth: zFiniteNumber.optional().describe("Maximum alias/import resolution depth (default: 8)"),
1888
- },
1902
+ })),
1889
1903
  handler: async (args) => {
1890
1904
  const opts = {};
1891
1905
  if (args.file_pattern != null)
@@ -1903,13 +1917,13 @@ const TOOL_DEFINITIONS = [
1903
1917
  requiresLanguage: "python",
1904
1918
  searchHint: "python django view auth csrf login_required middleware mixin route security posture",
1905
1919
  description: "Assess effective Django view security from decorators, mixins, settings middleware, and optional route resolution.",
1906
- schema: {
1920
+ schema: lazySchema(() => ({
1907
1921
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1908
1922
  path: z.string().optional().describe("Django route path to resolve first, e.g. /settings/"),
1909
1923
  symbol_name: z.string().optional().describe("View function/class/method name when you already know the symbol"),
1910
1924
  file_pattern: z.string().optional().describe("Filter candidate symbols by file path substring"),
1911
1925
  settings_file: z.string().optional().describe("Explicit Django settings file path (auto-detects if omitted)"),
1912
- },
1926
+ })),
1913
1927
  handler: async (args) => {
1914
1928
  const opts = {};
1915
1929
  if (args.path != null)
@@ -1929,7 +1943,7 @@ const TOOL_DEFINITIONS = [
1929
1943
  requiresLanguage: "python",
1930
1944
  searchHint: "python django taint data flow source sink request get post redirect mark_safe cursor execute subprocess session trace",
1931
1945
  description: "Trace Python/Django user-controlled data from request sources to security sinks like redirect, mark_safe, cursor.execute, subprocess, requests/httpx, open, or session writes.",
1932
- schema: {
1946
+ schema: lazySchema(() => ({
1933
1947
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1934
1948
  framework: z.enum(["python-django"]).optional().describe("Currently only python-django is implemented"),
1935
1949
  file_pattern: z.string().optional().describe("Restrict analysis to matching Python files"),
@@ -1937,7 +1951,7 @@ const TOOL_DEFINITIONS = [
1937
1951
  sink_patterns: z.array(z.string()).optional().describe("Optional sink pattern allowlist (defaults to built-in security sinks)"),
1938
1952
  max_depth: zFiniteNumber.optional().describe("Maximum interprocedural helper depth (default: 4)"),
1939
1953
  max_traces: zFiniteNumber.optional().describe("Maximum traces to return before truncation (default: 50)"),
1940
- },
1954
+ })),
1941
1955
  handler: async (args) => {
1942
1956
  const opts = {};
1943
1957
  if (args.framework != null)
@@ -1961,13 +1975,13 @@ const TOOL_DEFINITIONS = [
1961
1975
  requiresLanguage: "python",
1962
1976
  searchHint: "python callers call site usage trace cross module import delay apply_async constructor",
1963
1977
  description: "Find all call sites of a Python symbol: direct calls, method calls, Celery .delay()/.apply_async(), constructor, references.",
1964
- schema: {
1978
+ schema: lazySchema(() => ({
1965
1979
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1966
1980
  target_name: z.string().describe("Name of the target function/class/method"),
1967
1981
  target_file: z.string().optional().describe("Disambiguate target by file path substring"),
1968
1982
  file_pattern: z.string().optional().describe("Restrict caller search scope"),
1969
1983
  max_results: zFiniteNumber.optional().describe("Max callers to return (default: 100)"),
1970
- },
1984
+ })),
1971
1985
  handler: async (args) => {
1972
1986
  const opts = {};
1973
1987
  if (args.target_file != null)
@@ -1985,10 +1999,10 @@ const TOOL_DEFINITIONS = [
1985
1999
  requiresLanguage: "python",
1986
2000
  searchHint: "python django settings security debug secret key allowed hosts csrf middleware cookie hsts cors",
1987
2001
  description: "Audit Django settings.py: 15 security/config checks (DEBUG, SECRET_KEY, CSRF, CORS, HSTS, cookies, sqlite, middleware).",
1988
- schema: {
2002
+ schema: lazySchema(() => ({
1989
2003
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1990
2004
  settings_file: z.string().optional().describe("Explicit settings file path (auto-detects if omitted)"),
1991
- },
2005
+ })),
1992
2006
  handler: async (args) => {
1993
2007
  const opts = {};
1994
2008
  if (args.settings_file != null)
@@ -2002,12 +2016,12 @@ const TOOL_DEFINITIONS = [
2002
2016
  requiresLanguage: "python",
2003
2017
  searchHint: "python mypy type check error strict return incompatible argument missing",
2004
2018
  description: "Run mypy type checker with symbol correlation. Parses error codes, maps to containing symbols.",
2005
- schema: {
2019
+ schema: lazySchema(() => ({
2006
2020
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2007
2021
  file_pattern: z.string().optional().describe("Filter by file path substring"),
2008
2022
  strict: zBool().describe("Enable mypy --strict mode"),
2009
2023
  max_results: zFiniteNumber.optional().describe("Max findings (default: 100)"),
2010
- },
2024
+ })),
2011
2025
  handler: async (args) => {
2012
2026
  const opts = {};
2013
2027
  if (args.file_pattern != null)
@@ -2025,12 +2039,12 @@ const TOOL_DEFINITIONS = [
2025
2039
  requiresLanguage: "python",
2026
2040
  searchHint: "python pyright type check reportMissingImports reportGeneralTypeIssues",
2027
2041
  description: "Run pyright type checker with symbol correlation. Parses JSON diagnostics, maps to containing symbols.",
2028
- schema: {
2042
+ schema: lazySchema(() => ({
2029
2043
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2030
2044
  file_pattern: z.string().optional().describe("Filter by file path substring"),
2031
2045
  strict: zBool().describe("Enable strict level"),
2032
2046
  max_results: zFiniteNumber.optional().describe("Max findings (default: 100)"),
2033
- },
2047
+ })),
2034
2048
  handler: async (args) => {
2035
2049
  const opts = {};
2036
2050
  if (args.file_pattern != null)
@@ -2048,11 +2062,11 @@ const TOOL_DEFINITIONS = [
2048
2062
  requiresLanguage: "python",
2049
2063
  searchHint: "python dependency version outdated vulnerable CVE pypi osv requirements pyproject",
2050
2064
  description: "Python dependency analysis: parse pyproject.toml/requirements.txt, detect unpinned deps, optional PyPI freshness, optional OSV.dev CVE scan.",
2051
- schema: {
2065
+ schema: lazySchema(() => ({
2052
2066
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2053
2067
  check_pypi: zBool().describe("Check PyPI for latest versions (network, opt-in)"),
2054
2068
  check_vulns: zBool().describe("Check OSV.dev for CVEs (network, opt-in)"),
2055
- },
2069
+ })),
2056
2070
  handler: async (args) => {
2057
2071
  const opts = {};
2058
2072
  if (args.check_pypi != null)
@@ -2068,12 +2082,12 @@ const TOOL_DEFINITIONS = [
2068
2082
  requiresLanguage: "python",
2069
2083
  searchHint: "python fastapi depends dependency injection security scopes oauth2 authentication auth endpoint",
2070
2084
  description: "Trace FastAPI Depends()/Security() dependency injection chains recursively from route handlers. Detects yield deps (resource cleanup), Security() with scopes, shared deps across endpoints, endpoints without auth.",
2071
- schema: {
2085
+ schema: lazySchema(() => ({
2072
2086
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2073
2087
  file_pattern: z.string().optional().describe("Filter by file path substring"),
2074
2088
  endpoint: z.string().optional().describe("Focus on a specific endpoint function name"),
2075
2089
  max_depth: zFiniteNumber.optional().describe("Max dependency tree depth (default: 5)"),
2076
- },
2090
+ })),
2077
2091
  handler: async (args) => {
2078
2092
  const opts = {};
2079
2093
  if (args.file_pattern != null)
@@ -2091,12 +2105,12 @@ const TOOL_DEFINITIONS = [
2091
2105
  requiresLanguage: "python",
2092
2106
  searchHint: "python async await asyncio blocking sync requests sleep subprocess django sqlalchemy ORM coroutine fastapi",
2093
2107
  description: "Detect 8 asyncio pitfalls in async def: blocking requests/sleep/IO/subprocess, sync SQLAlchemy/Django ORM in async views, async without await, asyncio.create_task without ref storage.",
2094
- schema: {
2108
+ schema: lazySchema(() => ({
2095
2109
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2096
2110
  file_pattern: z.string().optional().describe("Filter by file path substring"),
2097
2111
  rules: z.array(z.string()).optional().describe("Subset of rules to run"),
2098
2112
  max_results: zFiniteNumber.optional().describe("Max findings (default: 200)"),
2099
- },
2113
+ })),
2100
2114
  handler: async (args) => {
2101
2115
  const opts = {};
2102
2116
  if (args.file_pattern != null)
@@ -2114,11 +2128,11 @@ const TOOL_DEFINITIONS = [
2114
2128
  requiresLanguage: "python",
2115
2129
  searchHint: "python pydantic basemodel fastapi schema request response contract validator field constraint type classdiagram",
2116
2130
  description: "Extract Pydantic models: fields with types, validators, Field() constraints, model_config, cross-model references (list[X], Optional[Y]), inheritance. JSON or mermaid classDiagram.",
2117
- schema: {
2131
+ schema: lazySchema(() => ({
2118
2132
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2119
2133
  file_pattern: z.string().optional().describe("Filter by file path substring"),
2120
2134
  output_format: z.enum(["json", "mermaid"]).optional().describe("Output as structured JSON or mermaid classDiagram"),
2121
- },
2135
+ })),
2122
2136
  handler: async (args) => {
2123
2137
  const opts = {};
2124
2138
  if (args.file_pattern != null)
@@ -2134,11 +2148,11 @@ const TOOL_DEFINITIONS = [
2134
2148
  requiresLanguage: "python",
2135
2149
  searchHint: "python audit health score compound project review django security circular patterns celery dependencies dead code task shared_task delay apply_async chain group chord canvas retry orphan queue import cycle ImportError TYPE_CHECKING DFS",
2136
2150
  description: "Compound Python project health audit: circular imports + Django settings + anti-patterns (17) + framework wiring + Celery orphans + pytest fixtures + deps + dead code. Runs in parallel, returns unified health score (0-100) + severity counts + prioritized top_risks list.",
2137
- schema: {
2151
+ schema: lazySchema(() => ({
2138
2152
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2139
2153
  file_pattern: z.string().optional().describe("Filter by file path substring"),
2140
2154
  checks: z.array(z.string()).optional().describe("Subset of checks: circular_imports, django_settings, anti_patterns, framework_wiring, celery, pytest_fixtures, dependencies, dead_code"),
2141
- },
2155
+ })),
2142
2156
  handler: async (args) => {
2143
2157
  const opts = {};
2144
2158
  if (args.file_pattern != null)
@@ -2155,10 +2169,10 @@ const TOOL_DEFINITIONS = [
2155
2169
  requiresLanguage: "php",
2156
2170
  searchHint: "php namespace resolve PSR-4 autoload composer class file path yii2 laravel symfony",
2157
2171
  description: "Resolve a PHP FQCN to file path via composer.json PSR-4 autoload mapping.",
2158
- schema: {
2172
+ schema: lazySchema(() => ({
2159
2173
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2160
2174
  class_name: z.string().describe("Fully-qualified class name, e.g. 'App\\\\Models\\\\User'"),
2161
- },
2175
+ })),
2162
2176
  handler: async (args) => {
2163
2177
  return await resolvePhpNamespace(args.repo, args.class_name);
2164
2178
  },
@@ -2169,10 +2183,10 @@ const TOOL_DEFINITIONS = [
2169
2183
  requiresLanguage: "php",
2170
2184
  searchHint: "php event listener trigger handler chain yii2 laravel observer dispatch",
2171
2185
  description: "Trace PHP event → listener chains: find trigger() calls and matching on() handlers.",
2172
- schema: {
2186
+ schema: lazySchema(() => ({
2173
2187
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2174
2188
  event_name: z.string().optional().describe("Filter by specific event name"),
2175
- },
2189
+ })),
2176
2190
  handler: async (args) => {
2177
2191
  const opts = {};
2178
2192
  if (typeof args.event_name === "string")
@@ -2186,10 +2200,10 @@ const TOOL_DEFINITIONS = [
2186
2200
  requiresLanguage: "php",
2187
2201
  searchHint: "php view render template controller widget yii2 laravel blade",
2188
2202
  description: "Map PHP controller render() calls to view files. Yii2/Laravel convention-aware.",
2189
- schema: {
2203
+ schema: lazySchema(() => ({
2190
2204
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2191
2205
  controller: z.string().optional().describe("Filter by controller class name"),
2192
- },
2206
+ })),
2193
2207
  handler: async (args) => {
2194
2208
  const opts = {};
2195
2209
  if (typeof args.controller === "string")
@@ -2203,10 +2217,10 @@ const TOOL_DEFINITIONS = [
2203
2217
  requiresLanguage: "php",
2204
2218
  searchHint: "php service locator DI container component resolve yii2 laravel facade provider",
2205
2219
  description: "Resolve PHP service locator references (Yii::$app->X, Laravel facades) to concrete classes via config parsing.",
2206
- schema: {
2220
+ schema: lazySchema(() => ({
2207
2221
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2208
2222
  service_name: z.string().optional().describe("Filter by specific service name (e.g. 'db', 'user', 'cache')"),
2209
- },
2223
+ })),
2210
2224
  handler: async (args) => {
2211
2225
  const opts = {};
2212
2226
  if (typeof args.service_name === "string")
@@ -2220,11 +2234,11 @@ const TOOL_DEFINITIONS = [
2220
2234
  requiresLanguage: "php",
2221
2235
  searchHint: "php security scan audit vulnerability injection XSS CSRF SQL eval exec unserialize",
2222
2236
  description: "Scan PHP code for security vulnerabilities: SQL injection, XSS, eval, exec, unserialize, file inclusion. Parallel pattern checks.",
2223
- schema: {
2237
+ schema: lazySchema(() => ({
2224
2238
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2225
2239
  file_pattern: z.string().optional().describe("Glob pattern to filter scanned files (default: '*.php')"),
2226
2240
  checks: z.array(z.string()).optional().describe("Subset of checks to run: sql-injection-php, xss-php, eval-php, exec-php, unserialize-php, file-include-var, unescaped-yii-view, raw-query-yii"),
2227
- },
2241
+ })),
2228
2242
  handler: async (args) => {
2229
2243
  const opts = {};
2230
2244
  if (typeof args.file_pattern === "string")
@@ -2240,11 +2254,11 @@ const TOOL_DEFINITIONS = [
2240
2254
  requiresLanguage: "php",
2241
2255
  searchHint: "php project audit health quality technical debt code review comprehensive yii2 laravel activerecord eloquent model schema relations rules behaviors table orm n+1 query foreach eager loading relation god class anti-pattern too many methods oversized",
2242
2256
  description: "Compound PHP project audit: security scan + ActiveRecord analysis + N+1 detection + god model detection + health score. Runs checks in parallel.",
2243
- schema: {
2257
+ schema: lazySchema(() => ({
2244
2258
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2245
2259
  file_pattern: z.string().optional().describe("Glob pattern to filter analyzed files"),
2246
2260
  checks: z.string().optional().describe("Comma-separated checks: n_plus_one, god_model, activerecord, security, events, views, services, namespace. Default: all"),
2247
- },
2261
+ })),
2248
2262
  handler: async (args) => {
2249
2263
  const opts = {};
2250
2264
  if (typeof args.file_pattern === "string")
@@ -2261,11 +2275,11 @@ const TOOL_DEFINITIONS = [
2261
2275
  category: "conversations",
2262
2276
  searchHint: "consolidate memories dream knowledge MEMORY.md decisions solutions patterns",
2263
2277
  description: "Consolidate conversations into MEMORY.md — decisions, solutions, patterns.",
2264
- schema: {
2278
+ schema: lazySchema(() => ({
2265
2279
  project_path: z.string().optional().describe("Project path (auto-detects from cwd if omitted)"),
2266
2280
  output_path: z.string().optional().describe("Custom output file path (default: MEMORY.md in project root)"),
2267
2281
  min_confidence: z.enum(["high", "medium", "low"]).optional().describe("Minimum confidence level for extracted memories (default: low)"),
2268
- },
2282
+ })),
2269
2283
  handler: async (args) => {
2270
2284
  const opts = {};
2271
2285
  if (typeof args.output_path === "string")
@@ -2281,9 +2295,9 @@ const TOOL_DEFINITIONS = [
2281
2295
  category: "conversations",
2282
2296
  searchHint: "read memory MEMORY.md institutional knowledge past decisions",
2283
2297
  description: "Read MEMORY.md knowledge file with past decisions and patterns.",
2284
- schema: {
2298
+ schema: lazySchema(() => ({
2285
2299
  project_path: z.string().optional().describe("Project path (default: current directory)"),
2286
- },
2300
+ })),
2287
2301
  handler: async (args) => {
2288
2302
  const result = await readMemory(args.project_path);
2289
2303
  if (!result)
@@ -2297,7 +2311,7 @@ const TOOL_DEFINITIONS = [
2297
2311
  category: "meta",
2298
2312
  searchHint: "create plan multi-step analysis workflow coordinator scratchpad",
2299
2313
  description: "Create multi-step analysis plan with shared scratchpad and dependencies.",
2300
- schema: {
2314
+ schema: lazySchema(() => ({
2301
2315
  title: z.string().describe("Plan title describing the analysis goal"),
2302
2316
  steps: z.union([
2303
2317
  z.array(z.object({
@@ -2309,7 +2323,7 @@ const TOOL_DEFINITIONS = [
2309
2323
  })),
2310
2324
  z.string().transform((s) => JSON.parse(s)),
2311
2325
  ]).describe("Steps array: {description, tool, args, result_key?, depends_on?}. JSON string OK."),
2312
- },
2326
+ })),
2313
2327
  handler: async (args) => {
2314
2328
  const result = await createAnalysisPlan(args.title, args.steps);
2315
2329
  return result;
@@ -2320,11 +2334,11 @@ const TOOL_DEFINITIONS = [
2320
2334
  category: "meta",
2321
2335
  searchHint: "scratchpad write store knowledge cross-step data persist",
2322
2336
  description: "Write key-value to plan scratchpad for cross-step knowledge sharing.",
2323
- schema: {
2337
+ schema: lazySchema(() => ({
2324
2338
  plan_id: z.string().describe("Analysis plan identifier"),
2325
2339
  key: z.string().describe("Key name for the entry"),
2326
2340
  value: z.string().describe("Value to store"),
2327
- },
2341
+ })),
2328
2342
  handler: async (args) => writeScratchpad(args.plan_id, args.key, args.value),
2329
2343
  },
2330
2344
  {
@@ -2332,10 +2346,10 @@ const TOOL_DEFINITIONS = [
2332
2346
  category: "meta",
2333
2347
  searchHint: "scratchpad read retrieve knowledge entry",
2334
2348
  description: "Read a key from a plan's scratchpad. Returns the stored value or null if not found.",
2335
- schema: {
2349
+ schema: lazySchema(() => ({
2336
2350
  plan_id: z.string().describe("Analysis plan identifier"),
2337
2351
  key: z.string().describe("Key name to read"),
2338
- },
2352
+ })),
2339
2353
  handler: async (args) => {
2340
2354
  const result = await readScratchpad(args.plan_id, args.key);
2341
2355
  return result ?? { error: "Key not found in scratchpad" };
@@ -2346,9 +2360,9 @@ const TOOL_DEFINITIONS = [
2346
2360
  category: "meta",
2347
2361
  searchHint: "scratchpad list entries keys",
2348
2362
  description: "List all entries in a plan's scratchpad with their sizes.",
2349
- schema: {
2363
+ schema: lazySchema(() => ({
2350
2364
  plan_id: z.string().describe("Analysis plan identifier"),
2351
- },
2365
+ })),
2352
2366
  handler: (args) => listScratchpad(args.plan_id),
2353
2367
  },
2354
2368
  {
@@ -2356,12 +2370,12 @@ const TOOL_DEFINITIONS = [
2356
2370
  category: "meta",
2357
2371
  searchHint: "update step status plan progress completed failed",
2358
2372
  description: "Update step status in plan. Auto-updates plan status on completion.",
2359
- schema: {
2373
+ schema: lazySchema(() => ({
2360
2374
  plan_id: z.string().describe("Analysis plan identifier"),
2361
2375
  step_id: z.string().describe("Step identifier (e.g. step_1)"),
2362
2376
  status: z.enum(["pending", "in_progress", "completed", "failed", "skipped"]).describe("New status for the step"),
2363
2377
  error: z.string().optional().describe("Error message if status is 'failed'"),
2364
- },
2378
+ })),
2365
2379
  handler: async (args) => {
2366
2380
  const result = await updateStepStatus(args.plan_id, args.step_id, args.status, args.error);
2367
2381
  return result;
@@ -2372,9 +2386,9 @@ const TOOL_DEFINITIONS = [
2372
2386
  category: "meta",
2373
2387
  searchHint: "get plan status steps progress",
2374
2388
  description: "Get the current state of an analysis plan including all step statuses.",
2375
- schema: {
2389
+ schema: lazySchema(() => ({
2376
2390
  plan_id: z.string().describe("Analysis plan identifier"),
2377
- },
2391
+ })),
2378
2392
  handler: async (args) => {
2379
2393
  const plan = getPlan(args.plan_id);
2380
2394
  return plan ?? { error: "Plan not found" };
@@ -2385,7 +2399,7 @@ const TOOL_DEFINITIONS = [
2385
2399
  category: "meta",
2386
2400
  searchHint: "list plans active analysis workflows",
2387
2401
  description: "List all active analysis plans with their completion status.",
2388
- schema: {},
2402
+ schema: lazySchema(() => ({})),
2389
2403
  handler: async () => listPlans(),
2390
2404
  },
2391
2405
  // --- Review diff ---
@@ -2394,7 +2408,7 @@ const TOOL_DEFINITIONS = [
2394
2408
  category: "diff",
2395
2409
  searchHint: "review diff static analysis git changes secrets breaking-changes complexity dead-code blast-radius",
2396
2410
  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.",
2397
- schema: {
2411
+ schema: lazySchema(() => ({
2398
2412
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2399
2413
  since: z.string().optional().describe("Base git ref (default: HEAD~1)"),
2400
2414
  until: z.string().optional().describe("Target ref. Default: HEAD. Special: WORKING, STAGED"),
@@ -2403,7 +2417,7 @@ const TOOL_DEFINITIONS = [
2403
2417
  token_budget: zNum().describe("Max tokens (default: 15000)"),
2404
2418
  max_files: zNum().describe("Warn above N files (default: 50)"),
2405
2419
  check_timeout_ms: zNum().describe("Per-check timeout ms (default: 8000)"),
2406
- },
2420
+ })),
2407
2421
  handler: async (args) => {
2408
2422
  const checksArr = args.checks
2409
2423
  ? args.checks.split(",").map((c) => c.trim()).filter(Boolean)
@@ -2439,10 +2453,13 @@ const TOOL_DEFINITIONS = [
2439
2453
  searchHint: "usage statistics tool calls tokens timing metrics",
2440
2454
  outputSchema: OutputSchemas.usageStats,
2441
2455
  description: "Show usage statistics for all CodeSift tool calls (call counts, tokens, timing, repos)",
2442
- schema: {},
2456
+ schema: lazySchema(() => ({})),
2443
2457
  handler: async () => {
2444
2458
  const stats = await getUsageStats();
2445
- return { report: formatUsageReport(stats) };
2459
+ const { createRequire } = await import("node:module");
2460
+ const req = createRequire(import.meta.url);
2461
+ const pkgVersion = req("../package.json").version;
2462
+ return { version: pkgVersion, report: formatUsageReport(stats) };
2446
2463
  },
2447
2464
  },
2448
2465
  // ── Session context tools ───────────────────────────────────────────────
@@ -2451,9 +2468,9 @@ const TOOL_DEFINITIONS = [
2451
2468
  category: "session",
2452
2469
  searchHint: "session context snapshot compaction summary explored symbols files queries",
2453
2470
  description: "Get a compact ~200 token snapshot of what was explored in this session. Designed to survive context compaction. Call proactively before long tasks.",
2454
- schema: {
2471
+ schema: lazySchema(() => ({
2455
2472
  repo: z.string().optional().describe("Filter to specific repo. Default: most recent repo."),
2456
- },
2473
+ })),
2457
2474
  handler: async (args) => {
2458
2475
  return formatSnapshot(getSessionState(), args.repo);
2459
2476
  },
@@ -2463,10 +2480,10 @@ const TOOL_DEFINITIONS = [
2463
2480
  category: "session",
2464
2481
  searchHint: "session context full explored symbols files queries negative evidence",
2465
2482
  description: "Get full session context: explored symbols, files, queries, and negative evidence (searched but not found). Use get_session_snapshot for a compact version.",
2466
- schema: {
2483
+ schema: lazySchema(() => ({
2467
2484
  repo: z.string().optional().describe("Filter to specific repo"),
2468
2485
  include_stale: zBool().describe("Include stale negative evidence entries (default: false)"),
2469
- },
2486
+ })),
2470
2487
  handler: async (args) => {
2471
2488
  const includeStale = args.include_stale === true || args.include_stale === "true";
2472
2489
  return getContext(args.repo, includeStale);
@@ -2478,10 +2495,10 @@ const TOOL_DEFINITIONS = [
2478
2495
  category: "analysis",
2479
2496
  searchHint: "project profile stack conventions middleware routes rate-limits auth detection",
2480
2497
  description: "Analyze a repository to extract stack, file classifications, and framework-specific conventions. Returns a structured project profile (schema v1.0) with file:line evidence for convention-level facts.",
2481
- schema: {
2498
+ schema: lazySchema(() => ({
2482
2499
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2483
2500
  force: zBool().describe("Ignore cached results and re-analyze"),
2484
- },
2501
+ })),
2485
2502
  handler: async (args) => {
2486
2503
  const result = await analyzeProject(args.repo, {
2487
2504
  force: args.force,
@@ -2494,7 +2511,7 @@ const TOOL_DEFINITIONS = [
2494
2511
  category: "meta",
2495
2512
  searchHint: "extractor version cache invalidation profile parser languages",
2496
2513
  description: "Return parser_languages (tree-sitter symbol extractors) and profile_frameworks (analyze_project detectors). Text tools (search_text, get_file_tree) work on ALL files regardless — use this only for cache invalidation or to check symbol support for a specific language.",
2497
- schema: {},
2514
+ schema: lazySchema(() => ({})),
2498
2515
  handler: async () => getExtractorVersions(),
2499
2516
  },
2500
2517
  // --- Composite tools ---
@@ -2503,12 +2520,12 @@ const TOOL_DEFINITIONS = [
2503
2520
  category: "analysis",
2504
2521
  searchHint: "audit scan code quality CQ gates dead code clones complexity patterns",
2505
2522
  description: "Run 5 analysis tools in parallel, return findings keyed by CQ gate. One call replaces sequential find_dead_code + search_patterns + find_clones + analyze_complexity + analyze_hotspots. Returns: CQ8 (empty catch), CQ11 (complexity), CQ13 (dead code), CQ14 (clones), CQ17 (perf anti-patterns).",
2506
- schema: {
2523
+ schema: lazySchema(() => ({
2507
2524
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2508
2525
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
2509
2526
  include_tests: zBool().describe("Include test files (default: false)"),
2510
2527
  checks: z.string().optional().describe("Comma-separated CQ gates to check (default: all). E.g. 'CQ8,CQ11,CQ14'"),
2511
- },
2528
+ })),
2512
2529
  handler: async (args) => {
2513
2530
  const checks = args.checks ? args.checks.split(",").map(s => s.trim()) : undefined;
2514
2531
  const opts = {};
@@ -2528,9 +2545,9 @@ const TOOL_DEFINITIONS = [
2528
2545
  category: "meta",
2529
2546
  searchHint: "index status indexed repo check files symbols languages",
2530
2547
  description: "Check whether a repository is indexed and return index metadata: file count, symbol count, language breakdown, text_stub languages (no parser). Use this before calling symbol-based tools on unfamiliar repos.",
2531
- schema: {
2548
+ schema: lazySchema(() => ({
2532
2549
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2533
- },
2550
+ })),
2534
2551
  handler: async (args) => {
2535
2552
  const result = await indexStatus(args.repo);
2536
2553
  if (!result.indexed)
@@ -2555,13 +2572,13 @@ const TOOL_DEFINITIONS = [
2555
2572
  category: "analysis",
2556
2573
  searchHint: "performance perf hotspot N+1 unbounded query sync handler pagination findMany pLimit",
2557
2574
  description: "Scan for 6 performance anti-patterns: unbounded DB queries, sync I/O in handlers, N+1 loops, unbounded Promise.all, missing pagination, expensive recompute. Returns findings grouped by severity (high/medium/low) with fix hints.",
2558
- schema: {
2575
+ schema: lazySchema(() => ({
2559
2576
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2560
2577
  patterns: z.string().optional().describe("Comma-separated pattern names to check (default: all). Options: unbounded-query, sync-in-handler, n-plus-one, unbounded-parallel, missing-pagination, expensive-recompute"),
2561
2578
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
2562
2579
  include_tests: zBool().describe("Include test files (default: false)"),
2563
2580
  max_results: zNum().describe("Max findings to return (default: 50)"),
2564
- },
2581
+ })),
2565
2582
  handler: async (args) => {
2566
2583
  const patterns = args.patterns
2567
2584
  ? args.patterns.split(",").map((s) => s.trim()).filter(Boolean)
@@ -2584,13 +2601,13 @@ const TOOL_DEFINITIONS = [
2584
2601
  category: "architecture",
2585
2602
  searchHint: "fan-in fan-out coupling dependencies imports hub afferent efferent instability threshold",
2586
2603
  description: "Analyze import graph to find most-imported files (fan-in), most-dependent files (fan-out), and hub files (high both — instability risk). Returns coupling score 0-100. Use min_fan_in/min_fan_out for threshold-based audits ('all files with fan_in > 50') instead of top_n cap.",
2587
- schema: {
2604
+ schema: lazySchema(() => ({
2588
2605
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2589
2606
  path: z.string().optional().describe("Focus on files in this directory"),
2590
2607
  top_n: zNum().describe("How many entries per list (default: 20)"),
2591
2608
  min_fan_in: zNum().describe("Only return files with fan_in >= this value (default: 0). Use for audits."),
2592
2609
  min_fan_out: zNum().describe("Only return files with fan_out >= this value (default: 0). Use for audits."),
2593
- },
2610
+ })),
2594
2611
  handler: async (args) => {
2595
2612
  const opts = {};
2596
2613
  if (args.path != null)
@@ -2610,14 +2627,14 @@ const TOOL_DEFINITIONS = [
2610
2627
  category: "architecture",
2611
2628
  searchHint: "co-change temporal coupling git history Jaccard co-commit correlation cluster",
2612
2629
  description: "Analyze git history to find files that frequently change together (temporal coupling). Returns file pairs ranked by Jaccard similarity, plus clusters of always-co-changed files. Useful for detecting hidden dependencies.",
2613
- schema: {
2630
+ schema: lazySchema(() => ({
2614
2631
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2615
2632
  since_days: zNum().describe("Analyze last N days of history (default: 180)"),
2616
2633
  min_support: zNum().describe("Minimum co-commits to include a pair (default: 3)"),
2617
2634
  min_jaccard: zNum().describe("Minimum Jaccard similarity threshold (default: 0.3)"),
2618
2635
  path: z.string().optional().describe("Focus on files in this directory"),
2619
2636
  top_n: zNum().describe("Max pairs to return (default: 30)"),
2620
- },
2637
+ })),
2621
2638
  handler: async (args) => {
2622
2639
  const opts = {};
2623
2640
  if (args.since_days != null)
@@ -2639,12 +2656,12 @@ const TOOL_DEFINITIONS = [
2639
2656
  category: "architecture",
2640
2657
  searchHint: "architecture summary overview structure stack framework communities coupling circular dependencies entry points",
2641
2658
  description: "One-call architecture profile: stack detection, module communities, coupling hotspots, circular dependencies, LOC distribution, and entry points. Runs 5 analyses in parallel. Supports Mermaid diagram output.",
2642
- schema: {
2659
+ schema: lazySchema(() => ({
2643
2660
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2644
2661
  focus: z.string().optional().describe("Focus on this directory path"),
2645
2662
  output_format: z.enum(["text", "mermaid"]).optional().describe("Output format (default: text)"),
2646
2663
  token_budget: zNum().describe("Max tokens for output"),
2647
- },
2664
+ })),
2648
2665
  handler: async (args) => {
2649
2666
  const opts = {};
2650
2667
  if (args.focus != null)
@@ -2662,10 +2679,10 @@ const TOOL_DEFINITIONS = [
2662
2679
  category: "analysis",
2663
2680
  searchHint: "explain query SQL Prisma ORM database performance EXPLAIN ANALYZE findMany pagination index",
2664
2681
  description: "Parse a Prisma call and generate approximate SQL with EXPLAIN ANALYZE. Detects: unbounded queries, N+1 risks from includes, missing indexes. MVP: Prisma only. Supports postgresql/mysql/sqlite dialects.",
2665
- schema: {
2682
+ schema: lazySchema(() => ({
2666
2683
  code: z.string().describe("Prisma code snippet (e.g. prisma.user.findMany({...}))"),
2667
2684
  dialect: z.enum(["postgresql", "mysql", "sqlite"]).optional().describe("SQL dialect (default: postgresql)"),
2668
- },
2685
+ })),
2669
2686
  handler: async (args) => {
2670
2687
  const eqOpts = {};
2671
2688
  if (args.dialect != null)
@@ -2697,10 +2714,10 @@ const TOOL_DEFINITIONS = [
2697
2714
  category: "nestjs",
2698
2715
  searchHint: "nestjs audit analysis comprehensive module di guard route lifecycle pattern graphql websocket schedule typeorm microservice hook onModuleInit onApplicationBootstrap shutdown dependency graph circular import boundary injection provider constructor inject cycle interceptor pipe filter middleware chain security endpoint api map inventory list all params resolver query mutation subscription apollo gateway subscribemessage socketio realtime event cron interval timeout scheduled job task onevent listener entity relation onetomany manytoone database schema messagepattern eventpattern kafka rabbitmq nats transport request pipeline handler execution flow visualization bull bullmq queue processor process background worker scope transient singleton performance escalation swagger openapi documentation apiproperty apioperation apiresponse contract extract",
2699
2716
  description: "One-call NestJS architecture audit: modules, DI, guards, routes, lifecycle, patterns, GraphQL, WebSocket, schedule, TypeORM, microservices.",
2700
- schema: {
2717
+ schema: lazySchema(() => ({
2701
2718
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2702
2719
  checks: z.string().optional().describe("Comma-separated checks (default: all). Options: modules,routes,di,guards,lifecycle,patterns,graphql,websocket,schedule,typeorm,microservice"),
2703
- },
2720
+ })),
2704
2721
  handler: async (args) => {
2705
2722
  const checks = args.checks?.split(",").map((s) => s.trim()).filter(Boolean);
2706
2723
  return nestAudit(args.repo ?? "", checks ? { checks } : undefined);
@@ -2712,11 +2729,11 @@ const TOOL_DEFINITIONS = [
2712
2729
  category: "meta",
2713
2730
  searchHint: "audit agent config CLAUDE.md cursorrules stale symbols dead paths token waste redundancy",
2714
2731
  description: "Scan a config file (CLAUDE.md, .cursorrules) for stale symbol references, dead file paths, token cost, and redundancy. Cross-references against the CodeSift index. Optionally compares two config files for redundant content blocks.",
2715
- schema: {
2732
+ schema: lazySchema(() => ({
2716
2733
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2717
2734
  config_path: z.string().optional().describe("Path to config file (default: CLAUDE.md in repo root)"),
2718
2735
  compare_with: z.string().optional().describe("Path to second config file for redundancy detection"),
2719
- },
2736
+ })),
2720
2737
  handler: async (args) => {
2721
2738
  const opts = {};
2722
2739
  if (args.config_path != null)
@@ -2757,11 +2774,11 @@ const TOOL_DEFINITIONS = [
2757
2774
  category: "analysis",
2758
2775
  searchHint: "test impact analysis affected tests changed files CI confidence which tests to run",
2759
2776
  description: "Determine which tests to run based on changed files. Uses impact analysis, co-change correlation, and naming convention matching. Returns prioritized test list with confidence scores and a suggested test command.",
2760
- schema: {
2777
+ schema: lazySchema(() => ({
2761
2778
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2762
2779
  since: z.string().optional().describe("Git ref to compare from (default: HEAD~1)"),
2763
2780
  until: z.string().optional().describe("Git ref to compare to (default: HEAD)"),
2764
- },
2781
+ })),
2765
2782
  handler: async (args) => {
2766
2783
  const opts = {};
2767
2784
  if (args.since != null)
@@ -2790,12 +2807,12 @@ const TOOL_DEFINITIONS = [
2790
2807
  category: "analysis",
2791
2808
  searchHint: "dependency audit npm vulnerabilities CVE licenses outdated freshness lockfile drift supply chain",
2792
2809
  description: "Composite dependency health check: vulnerabilities (npm/pnpm/yarn audit), licenses (problematic copyleft detection), freshness (outdated count + major gaps), lockfile integrity (drift, duplicates). Runs 4 sub-checks in parallel. Replaces ~40 manual bash calls for D1-D5 audit dimensions.",
2793
- schema: {
2810
+ schema: lazySchema(() => ({
2794
2811
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2795
2812
  workspace_path: z.string().optional().describe("Workspace path (default: index root)"),
2796
2813
  skip_licenses: zBool().describe("Skip license check (faster, default: false)"),
2797
2814
  min_severity: z.enum(["low", "moderate", "high", "critical"]).optional().describe("Filter vulnerabilities by minimum severity"),
2798
- },
2815
+ })),
2799
2816
  handler: async (args) => {
2800
2817
  const opts = {};
2801
2818
  if (args.workspace_path != null)
@@ -2841,12 +2858,12 @@ const TOOL_DEFINITIONS = [
2841
2858
  category: "analysis",
2842
2859
  searchHint: "migration lint squawk SQL postgresql safety linter unsafe-migration not-null drop-column alter-column-type concurrently",
2843
2860
  description: "PostgreSQL migration safety linter via squawk wrapper. Detects 30+ anti-patterns: NOT NULL without default, DROP COLUMN, ALTER COLUMN TYPE, CREATE INDEX without CONCURRENTLY, etc. Requires squawk CLI installed (brew install squawk OR cargo install squawk-cli). Auto-discovers prisma/migrations, migrations/, db/migrate, drizzle/.",
2844
- schema: {
2861
+ schema: lazySchema(() => ({
2845
2862
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2846
2863
  migration_glob: z.string().optional().describe("Custom migration file glob pattern"),
2847
2864
  excluded_rules: z.union([z.array(z.string()), z.string().transform((s) => s.split(",").map((x) => x.trim()))]).optional().describe("Squawk rules to exclude (comma-sep or array)"),
2848
2865
  pg_version: z.string().optional().describe("PostgreSQL version for version-aware rules (e.g. '13')"),
2849
- },
2866
+ })),
2850
2867
  handler: async (args) => {
2851
2868
  const opts = {};
2852
2869
  if (args.migration_glob != null)
@@ -2881,10 +2898,10 @@ const TOOL_DEFINITIONS = [
2881
2898
  category: "analysis",
2882
2899
  searchHint: "prisma schema analyze ast model field index foreign-key relation soft-delete enum coverage",
2883
2900
  description: "Parse schema.prisma into structured AST. Returns model coverage: fields, indexes, FKs, relations, soft-delete detection, FK index coverage %, unindexed FKs (audit warning), status-as-String suggestions. Uses @mrleebo/prisma-ast for proper AST parsing (vs regex-only extractor).",
2884
- schema: {
2901
+ schema: lazySchema(() => ({
2885
2902
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2886
2903
  schema_path: z.string().optional().describe("Path to schema.prisma (default: auto-detected)"),
2887
- },
2904
+ })),
2888
2905
  handler: async (args) => {
2889
2906
  const opts = {};
2890
2907
  if (args.schema_path != null)
@@ -2926,11 +2943,11 @@ const TOOL_DEFINITIONS = [
2926
2943
  category: "analysis",
2927
2944
  searchHint: "astro islands client hydration directives framework",
2928
2945
  description: "Analyze Astro islands (client:* directives) in a repo. Finds all interactive components with hydration directives, lists server islands with fallback status, and optionally generates optimization recommendations.",
2929
- schema: {
2946
+ schema: lazySchema(() => ({
2930
2947
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2931
2948
  path_prefix: z.string().optional().describe("Only scan files under this path prefix"),
2932
2949
  include_recommendations: z.boolean().default(true).describe("Include optimization recommendations (default: true)"),
2933
- },
2950
+ })),
2934
2951
  handler: async (args) => {
2935
2952
  const opts = {};
2936
2953
  if (args.repo != null)
@@ -2947,12 +2964,12 @@ const TOOL_DEFINITIONS = [
2947
2964
  category: "analysis",
2948
2965
  searchHint: "astro hydration audit anti-patterns client load",
2949
2966
  description: "Audit Astro hydration usage for anti-patterns such as client:load on heavy components, missing client directives, or suboptimal hydration strategies. Returns issues grouped by severity with a letter grade.",
2950
- schema: {
2967
+ schema: lazySchema(() => ({
2951
2968
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2952
2969
  severity: z.enum(["all", "warnings", "errors"]).default("all").describe("Filter issues by severity (default: all)"),
2953
2970
  path_prefix: z.string().optional().describe("Only scan files under this path prefix"),
2954
2971
  fail_on: z.enum(["error", "warning", "info"]).optional().describe("Set exit_code gate: 'error' exits 1 on any errors; 'warning' exits 2 on warnings; 'info' exits 2 on info or warnings"),
2955
- },
2972
+ })),
2956
2973
  handler: async (args) => {
2957
2974
  const opts = {};
2958
2975
  if (args.repo != null)
@@ -2971,11 +2988,11 @@ const TOOL_DEFINITIONS = [
2971
2988
  category: "navigation",
2972
2989
  searchHint: "astro routes pages endpoints file-based routing",
2973
2990
  description: "Map all Astro routes (pages + API endpoints) discovered from the file-based routing structure. Returns routes with type, dynamic params, and handler symbols. Supports json/tree/table output formats.",
2974
- schema: {
2991
+ schema: lazySchema(() => ({
2975
2992
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2976
2993
  include_endpoints: z.boolean().default(true).describe("Include API endpoint routes (default: true)"),
2977
2994
  output_format: z.enum(["json", "tree", "table"]).default("json").describe("Output format: json | tree | table (default: json)"),
2978
- },
2995
+ })),
2979
2996
  handler: async (args) => {
2980
2997
  const opts = {};
2981
2998
  if (args.repo != null)
@@ -2992,9 +3009,9 @@ const TOOL_DEFINITIONS = [
2992
3009
  category: "analysis",
2993
3010
  searchHint: "astro config integrations adapter output mode",
2994
3011
  description: "Analyze an Astro project's configuration file (astro.config.mjs/ts/js). Extracts output mode (static/server/hybrid), adapter, integrations, site URL, and base path. Identifies dynamic/unresolved config.",
2995
- schema: {
3012
+ schema: lazySchema(() => ({
2996
3013
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2997
- },
3014
+ })),
2998
3015
  handler: async (args) => {
2999
3016
  const index = await getCodeIndex(args.repo ?? "");
3000
3017
  if (!index)
@@ -3007,10 +3024,10 @@ const TOOL_DEFINITIONS = [
3007
3024
  category: "analysis",
3008
3025
  searchHint: "astro actions defineAction zod refine passthrough multipart file enctype audit",
3009
3026
  description: "Audit Astro Actions (src/actions/index.ts) for 6 known anti-patterns (AA01-AA06): missing handler return, top-level .refine() (Astro issue #11641), .passthrough() usage (issue #11693), File schema without multipart form, server-side invocation via actions.xxx(), and client calls to unknown actions. Returns issues grouped by severity with an A/B/C/D score.",
3010
- schema: {
3027
+ schema: lazySchema(() => ({
3011
3028
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3012
3029
  severity: z.enum(["all", "warnings", "errors"]).default("all").describe("Filter issues by severity (default: all)"),
3013
- },
3030
+ })),
3014
3031
  handler: async (args) => {
3015
3032
  const opts = {};
3016
3033
  if (args.repo != null)
@@ -3025,10 +3042,10 @@ const TOOL_DEFINITIONS = [
3025
3042
  category: "analysis",
3026
3043
  searchHint: "astro content collections defineCollection zod schema reference glob loader frontmatter",
3027
3044
  description: "Parse an Astro content collections config (src/content.config.ts or legacy src/content/config.ts), extract each collection's loader + Zod schema fields, build a reference() graph, and optionally validate entry frontmatter against required fields.",
3028
- schema: {
3045
+ schema: lazySchema(() => ({
3029
3046
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3030
3047
  validate_entries: z.boolean().default(true).describe("Validate entry frontmatter against required schema fields (default: true)"),
3031
- },
3048
+ })),
3032
3049
  handler: async (args) => {
3033
3050
  const index = await getCodeIndex(args.repo ?? "");
3034
3051
  if (!index)
@@ -3044,10 +3061,10 @@ const TOOL_DEFINITIONS = [
3044
3061
  category: "analysis",
3045
3062
  searchHint: "astro meta audit full health check score gates recommendations islands hydration routes config actions content migration patterns",
3046
3063
  description: "One-call Astro project health check: runs all 7 Astro tools + 13 Astro patterns in parallel, returns unified {score, gates, sections, recommendations}. Mirrors react_quickstart pattern.",
3047
- schema: {
3064
+ schema: lazySchema(() => ({
3048
3065
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3049
3066
  skip: z.array(z.string()).optional().describe("Sections to skip: config, hydration, routes, actions, content, migration, patterns"),
3050
- },
3067
+ })),
3051
3068
  handler: async (args) => {
3052
3069
  const opts = {};
3053
3070
  if (args.repo != null)
@@ -3063,13 +3080,13 @@ const TOOL_DEFINITIONS = [
3063
3080
  category: "graph",
3064
3081
  searchHint: "hono middleware chain trace order scope auth use conditional applied_when if method header path basicAuth gated",
3065
3082
  description: "Hono middleware introspection. Three query modes: (1) route mode — pass path (+optional method) to get the chain effective for that route; (2) scope mode — pass scope literal (e.g. '/posts/*') to get that specific app.use chain; (3) app-wide mode — omit path and scope to get every chain flattened. Any mode supports only_conditional=true to filter to entries with applied_when populated, so the blog-API pattern (basicAuth wrapped in `if (method !== 'GET')`) is surfaced as gated rather than missed. Absorbs the former trace_conditional_middleware tool.",
3066
- schema: {
3083
+ schema: lazySchema(() => ({
3067
3084
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3068
3085
  path: z.string().optional().describe("Route path to look up (e.g. '/api/users/:id'). Omit for scope or app-wide query."),
3069
3086
  method: z.string().optional().describe("HTTP method filter (GET, POST, etc.). Only used in route mode."),
3070
3087
  scope: z.string().optional().describe("Exact middleware scope literal (e.g. '/posts/*'). Mutually exclusive with path."),
3071
3088
  only_conditional: z.boolean().optional().describe("Filter entries to those whose applied_when field is populated (conditional middleware)."),
3072
- },
3089
+ })),
3073
3090
  handler: async (args) => {
3074
3091
  const { traceMiddlewareChain } = await import("./tools/hono-middleware-chain.js");
3075
3092
  const opts = {};
@@ -3085,11 +3102,11 @@ const TOOL_DEFINITIONS = [
3085
3102
  category: "analysis",
3086
3103
  searchHint: "hono overview analyze app routes middleware runtime env bindings rpc",
3087
3104
  description: "Complete Hono application overview: routes grouped by method/scope, middleware map, context vars, OpenAPI status, RPC exports (flags Issue #3869 slow pattern), runtime, env bindings. One call for full project analysis.",
3088
- schema: {
3105
+ schema: lazySchema(() => ({
3089
3106
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3090
3107
  entry_file: z.string().optional().describe("Hono entry file (auto-detected if omitted)"),
3091
3108
  force_refresh: z.boolean().optional().describe("Clear cache and rebuild"),
3092
- },
3109
+ })),
3093
3110
  handler: async (args) => {
3094
3111
  const { analyzeHonoApp } = await import("./tools/hono-analyze-app.js");
3095
3112
  return await analyzeHonoApp(args.repo, args.entry_file, args.force_refresh);
@@ -3100,10 +3117,10 @@ const TOOL_DEFINITIONS = [
3100
3117
  category: "analysis",
3101
3118
  searchHint: "hono context flow c.set c.get c.var c.env middleware variable unguarded",
3102
3119
  description: "Trace Hono context variable flow (c.set/c.get/c.var/c.env). Detects MISSING_CONTEXT_VARIABLE findings where routes access variables that no middleware in their scope sets.",
3103
- schema: {
3120
+ schema: lazySchema(() => ({
3104
3121
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3105
3122
  variable: z.string().optional().describe("Specific variable name to trace (default: all)"),
3106
- },
3123
+ })),
3107
3124
  handler: async (args) => {
3108
3125
  const { traceContextFlow } = await import("./tools/hono-context-flow.js");
3109
3126
  return await traceContextFlow(args.repo, args.variable);
@@ -3114,11 +3131,11 @@ const TOOL_DEFINITIONS = [
3114
3131
  category: "analysis",
3115
3132
  searchHint: "hono openapi contract api schema createRoute zValidator",
3116
3133
  description: "Extract OpenAPI-style API contract from a Hono app. Uses explicit createRoute() definitions when available, infers from regular routes otherwise. Format: 'openapi' (paths object) or 'summary' (table).",
3117
- schema: {
3134
+ schema: lazySchema(() => ({
3118
3135
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3119
3136
  entry_file: z.string().optional().describe("Hono entry file (auto-detected if omitted)"),
3120
3137
  format: z.enum(["openapi", "summary"]).optional().describe("Output format (default: openapi)"),
3121
- },
3138
+ })),
3122
3139
  handler: async (args) => {
3123
3140
  const { extractApiContract } = await import("./tools/hono-api-contract.js");
3124
3141
  return await extractApiContract(args.repo, args.entry_file, args.format);
@@ -3129,9 +3146,9 @@ const TOOL_DEFINITIONS = [
3129
3146
  category: "analysis",
3130
3147
  searchHint: "hono rpc client type export typeof slow pattern Issue 3869 compile time",
3131
3148
  description: "Analyze Hono RPC type exports. Detects the slow `export type X = typeof app` pattern from Issue #3869 (8-min CI compile time) and recommends splitting into per-route-group types.",
3132
- schema: {
3149
+ schema: lazySchema(() => ({
3133
3150
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3134
- },
3151
+ })),
3135
3152
  handler: async (args) => {
3136
3153
  const { traceRpcTypes } = await import("./tools/hono-rpc-types.js");
3137
3154
  return await traceRpcTypes(args.repo);
@@ -3142,9 +3159,9 @@ const TOOL_DEFINITIONS = [
3142
3159
  category: "security",
3143
3160
  searchHint: "hono security audit rate limit secure headers auth order csrf env regression createMiddleware BlankEnv Issue 3587",
3144
3161
  description: "Security + type-safety audit of a Hono app. Rules: missing-secure-headers (global), missing-rate-limit + missing-auth (mutation routes, conditional-middleware aware via applied_when), auth-ordering (auth after non-auth in chain), env-regression (plain createMiddleware in 3+ chains — Hono Issue #3587, absorbed from the former detect_middleware_env_regression tool). Returns prioritized findings plus heuristic disclaimers via `notes` field for best-effort rules.",
3145
- schema: {
3162
+ schema: lazySchema(() => ({
3146
3163
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3147
- },
3164
+ })),
3148
3165
  handler: async (args) => {
3149
3166
  const { auditHonoSecurity } = await import("./tools/hono-security.js");
3150
3167
  return await auditHonoSecurity(args.repo);
@@ -3155,10 +3172,10 @@ const TOOL_DEFINITIONS = [
3155
3172
  category: "reporting",
3156
3173
  searchHint: "hono routes visualize mermaid tree diagram documentation",
3157
3174
  description: "Produce a visualization of Hono routing topology. Supports 'mermaid' (diagram) and 'tree' (ASCII) formats.",
3158
- schema: {
3175
+ schema: lazySchema(() => ({
3159
3176
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3160
3177
  format: z.enum(["mermaid", "tree"]).optional().describe("Output format (default: tree)"),
3161
- },
3178
+ })),
3162
3179
  handler: async (args) => {
3163
3180
  const { visualizeHonoRoutes } = await import("./tools/hono-visualize.js");
3164
3181
  return await visualizeHonoRoutes(args.repo, args.format);
@@ -3170,11 +3187,11 @@ const TOOL_DEFINITIONS = [
3170
3187
  category: "analysis",
3171
3188
  searchHint: "hono inline handler analyze c.json c.text status response error db fetch context",
3172
3189
  description: "Structured body analysis for each Hono inline handler: responses (c.json/text/html/redirect/newResponse with status + shape_hint), errors (throw new HTTPException/Error), db calls (prisma/db/knex/drizzle/mongoose/supabase), fetch calls, c.set/get/var/env access, inline validators, has_try_catch. Optional method + path filter. Named-handler routes return empty.",
3173
- schema: {
3190
+ schema: lazySchema(() => ({
3174
3191
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3175
3192
  method: z.string().optional().describe("HTTP method filter (case-insensitive)"),
3176
3193
  path: z.string().optional().describe("Route path filter (exact match, e.g. '/users/:id')"),
3177
- },
3194
+ })),
3178
3195
  handler: async (args) => {
3179
3196
  const { analyzeInlineHandler } = await import("./tools/hono-inline-analyze.js");
3180
3197
  return await analyzeInlineHandler(args.repo, args.method, args.path);
@@ -3185,9 +3202,9 @@ const TOOL_DEFINITIONS = [
3185
3202
  category: "analysis",
3186
3203
  searchHint: "hono response types status codes error paths RPC client InferResponseType Issue 4270",
3187
3204
  description: "Aggregate statically-knowable response types per route: c.json/text/html/body/redirect/newResponse emissions + throw new HTTPException/Error entries with status codes. Closes Hono Issue #4270 — RPC clients can generate types that include error paths. Returns routes[] plus total_statuses across the app.",
3188
- schema: {
3205
+ schema: lazySchema(() => ({
3189
3206
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3190
- },
3207
+ })),
3191
3208
  handler: async (args) => {
3192
3209
  const { extractResponseTypes } = await import("./tools/hono-response-types.js");
3193
3210
  return await extractResponseTypes(args.repo);
@@ -3198,9 +3215,9 @@ const TOOL_DEFINITIONS = [
3198
3215
  category: "analysis",
3199
3216
  searchHint: "hono modules architecture cluster path prefix middleware bindings enterprise Issue 4121",
3200
3217
  description: "Cluster Hono routes into logical modules by 2-segment path prefix, rolling up middleware chains, env bindings (from inline_analysis context_access), and source files per module. Closes Hono Issue #4121 — surfaces the implicit module structure for architecture review of enterprise apps. No new AST walking; post-processes the existing HonoAppModel.",
3201
- schema: {
3218
+ schema: lazySchema(() => ({
3202
3219
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3203
- },
3220
+ })),
3204
3221
  handler: async (args) => {
3205
3222
  const { detectHonoModules } = await import("./tools/hono-modules.js");
3206
3223
  return await detectHonoModules(args.repo);
@@ -3211,9 +3228,9 @@ const TOOL_DEFINITIONS = [
3211
3228
  category: "analysis",
3212
3229
  searchHint: "hono dead routes unused RPC client caller refactor monorepo cleanup",
3213
3230
  description: "Heuristically flag Hono server routes whose path segments do not appear in any non-server .ts/.tsx/.js/.jsx source file in the repo. Useful in monorepos to identify server endpoints that no Hono RPC client calls after refactors. Fully-dynamic routes (`/:id` only) are skipped. Documented as best-effort via the result note field.",
3214
- schema: {
3231
+ schema: lazySchema(() => ({
3215
3232
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3216
- },
3233
+ })),
3217
3234
  handler: async (args) => {
3218
3235
  const { findDeadHonoRoutes } = await import("./tools/hono-dead-routes.js");
3219
3236
  return await findDeadHonoRoutes(args.repo);
@@ -3225,13 +3242,13 @@ const TOOL_DEFINITIONS = [
3225
3242
  category: "analysis",
3226
3243
  searchHint: "nextjs next.js route map app router pages router rendering strategy SSG SSR ISR edge middleware",
3227
3244
  description: "Complete Next.js route map with rendering strategy per route. Enumerates App Router and Pages Router conventions, reads route segment config exports (dynamic/revalidate/runtime), classifies each route as static/ssr/isr/edge/client, detects metadata exports, computes layout chain, and flags hybrid conflicts where the same URL is served by both routers.",
3228
- schema: {
3245
+ schema: lazySchema(() => ({
3229
3246
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3230
3247
  workspace: z.string().optional().describe("Monorepo workspace path, e.g. 'apps/web'"),
3231
3248
  router: z.enum(["app", "pages", "both"]).optional().describe("Which routers to scan (default 'both')"),
3232
3249
  include_metadata: z.boolean().optional().describe("Include metadata export detection (default true)"),
3233
3250
  max_routes: z.number().int().positive().optional().describe("Max routes to process (default 1000)"),
3234
- },
3251
+ })),
3235
3252
  handler: async (args) => {
3236
3253
  const opts = {};
3237
3254
  if (args.workspace != null)
@@ -3251,11 +3268,11 @@ const TOOL_DEFINITIONS = [
3251
3268
  category: "analysis",
3252
3269
  searchHint: "nextjs seo metadata title description og image audit canonical twitter json-ld",
3253
3270
  description: "Audit Next.js page metadata for SEO completeness with per-route scoring. Walks app/page.tsx files, extracts title/description/openGraph/canonical/twitter/JSON-LD via tree-sitter, scores each route 0-100 with a weighted formula, and aggregates a per-grade distribution + top issue list.",
3254
- schema: {
3271
+ schema: lazySchema(() => ({
3255
3272
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3256
3273
  workspace: z.string().optional().describe("Monorepo workspace path, e.g. 'apps/web'"),
3257
3274
  max_routes: z.number().int().positive().optional().describe("Max routes to process (default 1000)"),
3258
- },
3275
+ })),
3259
3276
  handler: async (args) => {
3260
3277
  const opts = {};
3261
3278
  if (args.workspace != null)
@@ -3271,13 +3288,13 @@ const TOOL_DEFINITIONS = [
3271
3288
  category: "analysis",
3272
3289
  searchHint: "nextjs next.js framework audit meta-tool overall score security metadata routes components classifier use client use server hooks server actions auth validation rate limit zod api contract route handler openapi method body schema response client boundary bundle imports loc link integrity broken navigation href router push 404 data flow fetch waterfall cache cookies headers ssr revalidate middleware coverage protected admin matcher",
3273
3290
  description: "Run all Next.js sub-audits (components, routes, metadata, security, api_contract, boundary, links, data_flow, middleware_coverage) and aggregate into a unified weighted overall score with grade. Use as a single first-call for any Next.js project.",
3274
- schema: {
3291
+ schema: lazySchema(() => ({
3275
3292
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3276
3293
  workspace: z.string().optional().describe("Monorepo workspace path, e.g. 'apps/web'"),
3277
3294
  tools: z.array(z.string()).optional().describe("Subset of tools to run (default: all 9). Names: components, routes, metadata, security, api_contract, boundary, links, data_flow, middleware_coverage"),
3278
3295
  mode: z.enum(["full", "priority"]).optional().describe("Output mode: 'full' returns per-tool results + aggregated summary; 'priority' returns a single unified top-N actionable findings list sorted by severity × cross-tool occurrences"),
3279
3296
  priority_limit: z.number().int().positive().optional().describe("Max findings in priority mode (default: 20)"),
3280
- },
3297
+ })),
3281
3298
  handler: async (args) => {
3282
3299
  const opts = {};
3283
3300
  if (args.workspace != null)
@@ -3298,12 +3315,12 @@ const TOOL_DEFINITIONS = [
3298
3315
  category: "analysis",
3299
3316
  searchHint: "SQL schema ERD entity relationship tables views columns foreign key database migration",
3300
3317
  description: "Analyze SQL schema: tables, views, columns, foreign keys, relationships. Output as JSON or Mermaid ERD.",
3301
- schema: {
3318
+ schema: lazySchema(() => ({
3302
3319
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3303
3320
  file_pattern: z.string().optional().describe("Filter SQL files by pattern (e.g. 'migrations/')"),
3304
3321
  output_format: z.enum(["json", "mermaid"]).optional().describe("Output format (default: json)"),
3305
3322
  include_columns: zBool().describe("Include column details in output (default: true)"),
3306
- },
3323
+ })),
3307
3324
  handler: async (args) => {
3308
3325
  const { analyzeSchema } = await import("./tools/sql-tools.js");
3309
3326
  const opts = {};
@@ -3345,13 +3362,13 @@ const TOOL_DEFINITIONS = [
3345
3362
  category: "analysis",
3346
3363
  searchHint: "SQL table query trace references cross-language ORM Prisma Drizzle migration",
3347
3364
  description: "Trace SQL table references across the codebase: DDL, DML, FK, and ORM models (Prisma, Drizzle).",
3348
- schema: {
3365
+ schema: lazySchema(() => ({
3349
3366
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3350
3367
  table: z.string().describe("Table name to trace (required)"),
3351
3368
  include_orm: zBool().describe("Check Prisma/Drizzle ORM models (default: true)"),
3352
3369
  file_pattern: z.string().optional().describe("Scope search to files matching pattern"),
3353
3370
  max_references: zNum().describe("Maximum references to return (default: 500)"),
3354
- },
3371
+ })),
3355
3372
  handler: async (args) => {
3356
3373
  const { traceQuery } = await import("./tools/sql-tools.js");
3357
3374
  const opts = {
@@ -3392,12 +3409,12 @@ const TOOL_DEFINITIONS = [
3392
3409
  category: "analysis",
3393
3410
  searchHint: "SQL audit composite drift orphan lint DML safety complexity god table schema diagnostic",
3394
3411
  description: "Composite SQL audit — runs 5 diagnostic gates (drift, orphan, lint, dml, complexity) in one call. Use this instead of calling the individual gate functions separately.",
3395
- schema: {
3412
+ schema: lazySchema(() => ({
3396
3413
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3397
3414
  checks: z.array(z.enum(["drift", "orphan", "lint", "dml", "complexity"])).optional().describe("Subset of gates to run (default: all 5)"),
3398
3415
  file_pattern: z.string().optional().describe("Scope to files matching pattern"),
3399
3416
  max_results: zNum().describe("Max DML findings per pattern (default: 200)"),
3400
- },
3417
+ })),
3401
3418
  handler: async (args) => {
3402
3419
  const { sqlAudit } = await import("./tools/sql-tools.js");
3403
3420
  const opts = {};
@@ -3431,10 +3448,10 @@ const TOOL_DEFINITIONS = [
3431
3448
  category: "analysis",
3432
3449
  searchHint: "migration diff SQL destructive DROP ALTER ADD schema change deploy risk",
3433
3450
  description: "Scan SQL migration files and classify operations as additive (CREATE TABLE), modifying (ALTER ADD), or destructive (DROP TABLE, DROP COLUMN, TRUNCATE). Flags deploy risks.",
3434
- schema: {
3451
+ schema: lazySchema(() => ({
3435
3452
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3436
3453
  file_pattern: z.string().optional().describe("Scope to migration files matching pattern"),
3437
- },
3454
+ })),
3438
3455
  handler: async (args) => {
3439
3456
  const { diffMigrations } = await import("./tools/sql-tools.js");
3440
3457
  const opts = {};
@@ -3466,14 +3483,14 @@ const TOOL_DEFINITIONS = [
3466
3483
  category: "search",
3467
3484
  searchHint: "search column SQL table field name type database schema find",
3468
3485
  description: "Search SQL columns across all tables by name (substring), type (int/string/float/...), or parent table. Returns column name, type, table, file, and line. Like search_symbols but scoped to SQL fields.",
3469
- schema: {
3486
+ schema: lazySchema(() => ({
3470
3487
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3471
3488
  query: z.string().describe("Column name substring to match (case-insensitive). Empty = no name filter."),
3472
3489
  type: z.string().optional().describe("Filter by normalized type: int, string, float, bool, datetime, json, uuid, bytes"),
3473
3490
  table: z.string().optional().describe("Filter by table name substring"),
3474
3491
  file_pattern: z.string().optional().describe("Scope to files matching pattern"),
3475
3492
  max_results: zNum().describe("Max columns to return (default: 100)"),
3476
- },
3493
+ })),
3477
3494
  handler: async (args) => {
3478
3495
  const { searchColumns } = await import("./tools/sql-tools.js");
3479
3496
  const opts = {
@@ -3502,10 +3519,10 @@ const TOOL_DEFINITIONS = [
3502
3519
  category: "analysis",
3503
3520
  searchHint: "astro v6 migration upgrade breaking changes compatibility check AM01 AM10 content collections ViewTransitions",
3504
3521
  description: "Scan an Astro project for v5→v6 breaking changes. Detects 10 issues (AM01–AM10): removed APIs (Astro.glob, emitESMImage), component renames (ViewTransitions→ClientRouter), content collection config changes, Node.js version requirements, Zod 4 deprecations, hybrid output mode, and removed integrations (@astrojs/lit). Returns a migration report with per-issue effort estimates.",
3505
- schema: {
3522
+ schema: lazySchema(() => ({
3506
3523
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3507
3524
  target_version: z.enum(["6"]).optional().describe("Target Astro version (default: '6')"),
3508
- },
3525
+ })),
3509
3526
  handler: async (args) => {
3510
3527
  const mcArgs = {};
3511
3528
  if (args.repo != null)
@@ -3544,12 +3561,12 @@ const TOOL_DEFINITIONS = [
3544
3561
  category: "discovery",
3545
3562
  searchHint: "plan turn routing recommend tools symbols files gap analysis session aware concierge",
3546
3563
  description: "Routes a natural-language query to the most relevant CodeSift tools, symbols, and files. Uses hybrid BM25+semantic ranking with session-aware dedup. Call at the start of a task to get a prioritized action list.",
3547
- schema: {
3564
+ schema: lazySchema(() => ({
3548
3565
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3549
3566
  query: z.string().describe("Natural-language description of what you want to do"),
3550
3567
  max_results: z.number().optional().describe("Max tools to return (default 10)"),
3551
3568
  skip_session: z.boolean().optional().describe("Skip session state checks (default false)"),
3552
- },
3569
+ })),
3553
3570
  handler: async (args) => {
3554
3571
  const { query, max_results, skip_session } = args;
3555
3572
  const opts = {};
@@ -3558,23 +3575,33 @@ const TOOL_DEFINITIONS = [
3558
3575
  if (skip_session !== undefined)
3559
3576
  opts.skip_session = skip_session;
3560
3577
  const result = await planTurn(args.repo, query, opts);
3578
+ for (const name of result.reveal_required) {
3579
+ enableToolByName(name);
3580
+ }
3561
3581
  return formatPlanTurnResult(result);
3562
3582
  },
3563
3583
  },
3564
3584
  ];
3585
+ const TOOL_DEFINITION_MAP = new Map(TOOL_DEFINITIONS.map((tool) => [tool.name, tool]));
3586
+ const TOOL_SUMMARIES = TOOL_DEFINITIONS.map((tool) => ({
3587
+ name: tool.name,
3588
+ category: tool.category,
3589
+ description: tool.description,
3590
+ searchHint: tool.searchHint,
3591
+ }));
3592
+ const TOOL_CATEGORIES = [...new Set(TOOL_SUMMARIES.map((summary) => summary.category).filter(Boolean))];
3593
+ const TOOL_PARAMS_CACHE = new Map();
3565
3594
  function buildToolSummaries() {
3566
- return TOOL_DEFINITIONS.map((t) => ({
3567
- name: t.name,
3568
- category: t.category,
3569
- description: t.description,
3570
- searchHint: t.searchHint,
3571
- }));
3595
+ return TOOL_SUMMARIES;
3572
3596
  }
3573
3597
  /**
3574
3598
  * Extract structured param info from a ToolDefinition's Zod schema.
3575
3599
  */
3576
3600
  function extractToolParams(def) {
3577
- return Object.entries(def.schema).map(([key, val]) => {
3601
+ const cached = TOOL_PARAMS_CACHE.get(def.name);
3602
+ if (cached)
3603
+ return cached;
3604
+ const params = Object.entries(def.schema).map(([key, val]) => {
3578
3605
  const zodVal = val;
3579
3606
  const isOptional = zodVal.isOptional?.() ?? false;
3580
3607
  return {
@@ -3583,6 +3610,8 @@ function extractToolParams(def) {
3583
3610
  description: zodVal.description ?? "",
3584
3611
  };
3585
3612
  });
3613
+ TOOL_PARAMS_CACHE.set(def.name, params);
3614
+ return params;
3586
3615
  }
3587
3616
  /**
3588
3617
  * Return full param details for a specific list of tool names.
@@ -3593,7 +3622,7 @@ export function describeTools(names) {
3593
3622
  const tools = [];
3594
3623
  const not_found = [];
3595
3624
  for (const name of capped) {
3596
- const def = TOOL_DEFINITIONS.find((t) => t.name === name);
3625
+ const def = TOOL_DEFINITION_MAP.get(name);
3597
3626
  if (!def) {
3598
3627
  not_found.push(name);
3599
3628
  continue;
@@ -3615,7 +3644,7 @@ export function describeTools(names) {
3615
3644
  export function discoverTools(query, category) {
3616
3645
  const summaries = buildToolSummaries();
3617
3646
  const queryTokens = query.toLowerCase().split(/\s+/).filter(Boolean);
3618
- const categories = [...new Set(summaries.map((s) => s.category).filter(Boolean))];
3647
+ const categories = TOOL_CATEGORIES;
3619
3648
  let filtered = summaries;
3620
3649
  if (category) {
3621
3650
  filtered = filtered.filter((s) => s.category === category);
@@ -3642,7 +3671,7 @@ export function discoverTools(query, category) {
3642
3671
  .slice(0, 15)
3643
3672
  .map((s) => {
3644
3673
  // Look up full definition to extract param info for deferred tools
3645
- const fullDef = TOOL_DEFINITIONS.find((t) => t.name === s.tool.name);
3674
+ const fullDef = TOOL_DEFINITION_MAP.get(s.tool.name);
3646
3675
  const params = fullDef
3647
3676
  ? extractToolParams(fullDef).map((p) => `${p.name}${p.required ? "" : "?"}: ${p.description || "string"}`)
3648
3677
  : [];
@@ -3683,22 +3712,16 @@ export function registerTools(server, options) {
3683
3712
  }
3684
3713
  // Clear handles from any previous registration (e.g. tests calling registerTools multiple times)
3685
3714
  toolHandles.clear();
3686
- // Register ALL tools with full schema; store returned handles
3687
- for (const tool of TOOL_DEFINITIONS) {
3688
- const handle = server.tool(tool.name, tool.description, tool.schema, async (args) => wrapTool(tool.name, args, () => tool.handler(args))());
3689
- toolHandles.set(tool.name, handle);
3690
- }
3691
- // Language-gated disabling — tools requiring a language absent from the
3692
- // project are disabled (still registered but hidden from ListTools).
3693
- // Users can re-enable via describe_tools(reveal=true) if needed.
3715
+ enabledFrameworkBundles.clear();
3716
+ registrationContext = { server, languages };
3717
+ // Register either the full catalog or only core tools. In deferred mode the
3718
+ // remaining tools are registered lazily via describe_tools(reveal=true),
3719
+ // plan_turn auto-reveal, or framework auto-load.
3694
3720
  for (const tool of TOOL_DEFINITIONS) {
3695
- if (!tool.requiresLanguage)
3721
+ if (deferNonCore && !CORE_TOOL_NAMES.has(tool.name)) {
3696
3722
  continue;
3697
- if (languages[tool.requiresLanguage])
3698
- continue;
3699
- const handle = toolHandles.get(tool.name);
3700
- if (handle)
3701
- handle.disable();
3723
+ }
3724
+ registerToolDefinition(server, tool, languages);
3702
3725
  }
3703
3726
  // Always register discover_tools meta-tool
3704
3727
  const discoverHandle = server.tool("discover_tools", "Search tool catalog by keyword or category. Returns matching tools with descriptions.", {
@@ -3716,30 +3739,19 @@ export function registerTools(server, options) {
3716
3739
  const result = describeTools(args.names);
3717
3740
  if (args.reveal === true) {
3718
3741
  for (const t of result.tools) {
3719
- const h = toolHandles.get(t.name);
3720
- if (h)
3721
- h.enable();
3742
+ enableToolByName(t.name);
3722
3743
  }
3723
3744
  }
3724
3745
  return result;
3725
3746
  })());
3726
3747
  toolHandles.set("describe_tools", describeHandle);
3727
- // In deferred mode, disable non-core tools (they remain registered but hidden from ListTools).
3728
- // LLM discovers them via discover_tools, then reveals with describe_tools(reveal: true).
3729
3748
  if (deferNonCore) {
3730
- for (const [name, handle] of toolHandles) {
3731
- if (!CORE_TOOL_NAMES.has(name) && name !== "discover_tools" && name !== "describe_tools") {
3732
- handle.disable();
3733
- }
3734
- }
3735
3749
  // Auto-enable framework-specific tools when project type is detected at CWD.
3736
3750
  // E.g. composer.json → enable PHP/Yii2 tools automatically.
3737
- detectAutoLoadTools(process.cwd())
3751
+ detectAutoLoadToolsCached(process.cwd())
3738
3752
  .then((toEnable) => {
3739
3753
  for (const name of toEnable) {
3740
- const h = toolHandles.get(name);
3741
- if (h)
3742
- h.enable();
3754
+ enableToolByName(name);
3743
3755
  }
3744
3756
  if (toEnable.length > 0) {
3745
3757
  console.error(`[codesift] Auto-loaded ${toEnable.length} framework tools for detected project type: ${toEnable.join(", ")}`);