codesift-mcp 0.5.3 → 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.
@@ -20,6 +20,35 @@ export const zNum = () => z.union([
20
20
  .transform((value) => Number(value))
21
21
  .pipe(zFiniteNumber),
22
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
+ }
23
52
  // ---------------------------------------------------------------------------
24
53
  // H11 — warn when symbol tools return empty for repos with text_stub languages
25
54
  // ---------------------------------------------------------------------------
@@ -95,6 +124,44 @@ const toolHandles = new Map();
95
124
  export function getToolHandle(name) {
96
125
  return toolHandles.get(name);
97
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
+ }
98
165
  /** Framework-specific tool bundles — auto-enabled when the framework is detected in an indexed repo */
99
166
  const FRAMEWORK_TOOL_BUNDLES = {
100
167
  nestjs: [
@@ -115,9 +182,7 @@ export function enableFrameworkToolBundle(framework) {
115
182
  return [];
116
183
  const enabled = [];
117
184
  for (const name of bundle) {
118
- const handle = toolHandles.get(name);
119
- if (handle && typeof handle.enable === "function") {
120
- handle.enable();
185
+ if (enableToolByName(name)) {
121
186
  enabled.push(name);
122
187
  }
123
188
  }
@@ -217,6 +282,8 @@ const HONO_TOOLS = [
217
282
  "detect_hono_modules",
218
283
  "find_dead_hono_routes",
219
284
  ];
285
+ const AUTO_LOAD_CACHE_TTL_MS = 5_000;
286
+ const autoLoadToolsCache = new Map();
220
287
  /**
221
288
  * Detect project type at CWD and return list of tools that should be auto-enabled.
222
289
  * Returns empty array if no framework-specific tools apply.
@@ -258,6 +325,24 @@ export async function detectAutoLoadTools(cwd) {
258
325
  }
259
326
  return toEnable;
260
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
+ }
261
346
  /**
262
347
  * Quick recursive scan for .tsx/.jsx files in common source dirs.
263
348
  * Limits depth to 3 and stops on first match to stay fast (<10ms on typical repos).
@@ -437,11 +522,11 @@ const TOOL_DEFINITIONS = [
437
522
  category: "indexing",
438
523
  searchHint: "index local folder directory project parse symbols",
439
524
  description: "Index a local folder, extracting symbols and building the search index",
440
- schema: {
525
+ schema: lazySchema(() => ({
441
526
  path: z.string().describe("Absolute path to the folder to index"),
442
527
  incremental: zBool().describe("Only re-index changed files"),
443
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."),
444
- },
529
+ })),
445
530
  handler: (args) => indexFolder(args.path, {
446
531
  incremental: args.incremental,
447
532
  include_paths: args.include_paths,
@@ -452,11 +537,11 @@ const TOOL_DEFINITIONS = [
452
537
  category: "indexing",
453
538
  searchHint: "clone remote git repository index",
454
539
  description: "Clone and index a remote git repository",
455
- schema: {
540
+ schema: lazySchema(() => ({
456
541
  url: z.string().describe("Git clone URL"),
457
542
  branch: z.string().optional().describe("Branch to checkout"),
458
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."),
459
- },
544
+ })),
460
545
  handler: (args) => indexRepo(args.url, {
461
546
  branch: args.branch,
462
547
  include_paths: args.include_paths,
@@ -468,10 +553,10 @@ const TOOL_DEFINITIONS = [
468
553
  searchHint: "list indexed repositories repos available",
469
554
  outputSchema: OutputSchemas.repoList,
470
555
  description: "List indexed repos. Only needed for multi-repo discovery — single-repo tools auto-resolve from CWD. Set compact=false for full metadata.",
471
- schema: {
556
+ schema: lazySchema(() => ({
472
557
  compact: zBool().describe("true=names only (default), false=full metadata"),
473
558
  name_contains: z.string().optional().describe("Filter repos by name substring (case-insensitive). E.g. 'tgm' matches 'local/tgm-panel'"),
474
- },
559
+ })),
475
560
  handler: (args) => {
476
561
  const opts = {
477
562
  compact: args.compact ?? true,
@@ -486,9 +571,9 @@ const TOOL_DEFINITIONS = [
486
571
  category: "indexing",
487
572
  searchHint: "clear cache invalidate re-index refresh",
488
573
  description: "Clear the index cache for a repository, forcing full re-index on next use",
489
- schema: {
574
+ schema: lazySchema(() => ({
490
575
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
491
- },
576
+ })),
492
577
  handler: (args) => invalidateCache(args.repo),
493
578
  },
494
579
  {
@@ -496,9 +581,9 @@ const TOOL_DEFINITIONS = [
496
581
  category: "indexing",
497
582
  searchHint: "re-index single file update incremental",
498
583
  description: "Re-index a single file after editing. Auto-finds repo, skips if unchanged.",
499
- schema: {
584
+ schema: lazySchema(() => ({
500
585
  path: z.string().describe("Absolute path to the file to re-index"),
501
- },
586
+ })),
502
587
  handler: (args) => indexFile(args.path),
503
588
  },
504
589
  // --- Search ---
@@ -508,7 +593,7 @@ const TOOL_DEFINITIONS = [
508
593
  searchHint: "search find symbols functions classes types methods by name signature",
509
594
  outputSchema: OutputSchemas.searchResults,
510
595
  description: "Search symbols by name/signature. Supports kind, file, and decorator filters. detail_level: compact (~15 tok), standard (default), full.",
511
- schema: {
596
+ schema: lazySchema(() => ({
512
597
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
513
598
  query: z.string().describe("Search query string"),
514
599
  kind: z.string().optional().describe("Filter by symbol kind (function, class, etc.)"),
@@ -520,7 +605,7 @@ const TOOL_DEFINITIONS = [
520
605
  detail_level: z.enum(["compact", "standard", "full"]).optional().describe("compact (~15 tok), standard (default), full (all source)"),
521
606
  token_budget: zNum().describe("Max tokens for results — greedily packs results until budget exhausted. Overrides top_k."),
522
607
  rerank: zBool().describe("Rerank results using cross-encoder model for improved relevance (requires @huggingface/transformers)"),
523
- },
608
+ })),
524
609
  handler: async (args) => {
525
610
  const results = await searchSymbols(args.repo, args.query, {
526
611
  kind: args.kind,
@@ -543,13 +628,13 @@ const TOOL_DEFINITIONS = [
543
628
  category: "search",
544
629
  searchHint: "AST tree-sitter query structural pattern matching code shape jsx react",
545
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.",
546
- schema: {
631
+ schema: lazySchema(() => ({
547
632
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
548
633
  query: z.string().describe("Tree-sitter query in S-expression syntax. For JSX/React use language='tsx'."),
549
634
  language: z.string().describe("Tree-sitter grammar: typescript, tsx, javascript, python, go, rust, java, ruby, php"),
550
635
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
551
636
  max_matches: zNum().describe("Maximum matches to return (default: 50)"),
552
- },
637
+ })),
553
638
  handler: async (args) => {
554
639
  const { astQuery } = await import("./tools/ast-query-tools.js");
555
640
  return astQuery(args.repo, args.query, {
@@ -564,14 +649,14 @@ const TOOL_DEFINITIONS = [
564
649
  category: "search",
565
650
  searchHint: "semantic meaning intent concept embedding vector natural language",
566
651
  description: "Search code by meaning using embeddings. For intent-based queries: 'error handling', 'auth flow'. Requires indexed embeddings.",
567
- schema: {
652
+ schema: lazySchema(() => ({
568
653
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
569
654
  query: z.string().describe("Natural language query describing what you're looking for"),
570
655
  top_k: zNum().describe("Number of results (default: 10)"),
571
656
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
572
657
  exclude_tests: zBool().describe("Exclude test files from results"),
573
658
  rerank: zBool().describe("Re-rank results with cross-encoder for better precision"),
574
- },
659
+ })),
575
660
  handler: async (args) => {
576
661
  const opts = {};
577
662
  if (args.top_k != null)
@@ -590,7 +675,7 @@ const TOOL_DEFINITIONS = [
590
675
  category: "search",
591
676
  searchHint: "full-text search grep regex keyword content files",
592
677
  description: "Full-text search across all files. For conceptual queries use semantic_search.",
593
- schema: {
678
+ schema: lazySchema(() => ({
594
679
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
595
680
  query: z.string().describe("Search query or regex pattern"),
596
681
  regex: zBool().describe("Treat query as a regex pattern"),
@@ -600,7 +685,7 @@ const TOOL_DEFINITIONS = [
600
685
  group_by_file: zBool().describe("Group by file: {file, count, lines[], first_match}. ~80% less output."),
601
686
  auto_group: zBool().describe("Auto group_by_file when >50 matches."),
602
687
  ranked: z.boolean().optional().describe("Classify hits by containing symbol and rank by centrality"),
603
- },
688
+ })),
604
689
  handler: (args) => searchText(args.repo, args.query, {
605
690
  regex: args.regex,
606
691
  context_lines: args.context_lines,
@@ -618,14 +703,14 @@ const TOOL_DEFINITIONS = [
618
703
  searchHint: "file tree directory structure listing files symbols",
619
704
  outputSchema: OutputSchemas.fileTree,
620
705
  description: "File tree with symbol counts. compact=true for flat list (10-50x less output). Cached 5min.",
621
- schema: {
706
+ schema: lazySchema(() => ({
622
707
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
623
708
  path_prefix: z.string().optional().describe("Filter to a subtree by path prefix"),
624
709
  name_pattern: z.string().optional().describe("Glob pattern to filter file names"),
625
710
  depth: zNum().describe("Maximum directory depth to traverse"),
626
711
  compact: zBool().describe("Return flat list of {path, symbols} instead of nested tree (much less output)"),
627
712
  min_symbols: zNum().describe("Only include files with at least this many symbols"),
628
- },
713
+ })),
629
714
  handler: async (args) => {
630
715
  const result = await getFileTree(args.repo, {
631
716
  path_prefix: args.path_prefix,
@@ -643,10 +728,10 @@ const TOOL_DEFINITIONS = [
643
728
  searchHint: "file outline symbols functions classes exports single file",
644
729
  outputSchema: OutputSchemas.fileOutline,
645
730
  description: "Get the symbol outline of a single file (functions, classes, exports)",
646
- schema: {
731
+ schema: lazySchema(() => ({
647
732
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
648
733
  file_path: z.string().describe("Relative file path within the repository"),
649
- },
734
+ })),
650
735
  handler: async (args) => {
651
736
  const result = await getFileOutline(args.repo, args.file_path);
652
737
  const output = formatFileOutline(result);
@@ -660,9 +745,9 @@ const TOOL_DEFINITIONS = [
660
745
  category: "outline",
661
746
  searchHint: "repository outline overview directory structure high-level",
662
747
  description: "Get a high-level outline of the entire repository grouped by directory",
663
- schema: {
748
+ schema: lazySchema(() => ({
664
749
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
665
- },
750
+ })),
666
751
  handler: async (args) => {
667
752
  const result = await getRepoOutline(args.repo);
668
753
  return formatRepoOutline(result);
@@ -673,9 +758,9 @@ const TOOL_DEFINITIONS = [
673
758
  category: "outline",
674
759
  searchHint: "suggest queries explore unfamiliar repo onboarding first call",
675
760
  description: "Suggest queries for exploring a new repo. Returns top files, kind distribution, examples.",
676
- schema: {
761
+ schema: lazySchema(() => ({
677
762
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
678
- },
763
+ })),
679
764
  handler: async (args) => {
680
765
  const result = await suggestQueries(args.repo);
681
766
  return formatSuggestQueries(result);
@@ -688,11 +773,11 @@ const TOOL_DEFINITIONS = [
688
773
  searchHint: "get retrieve single symbol source code by ID",
689
774
  outputSchema: OutputSchemas.symbol,
690
775
  description: "Get symbol by ID with source. Auto-prefetches children for classes. For batch: get_symbols. For context: get_context_bundle.",
691
- schema: {
776
+ schema: lazySchema(() => ({
692
777
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
693
778
  symbol_id: z.string().describe("Unique symbol identifier"),
694
779
  include_related: zBool().describe("Include children/related symbols (default: true)"),
695
- },
780
+ })),
696
781
  handler: async (args) => {
697
782
  const opts = {};
698
783
  if (args.include_related != null)
@@ -714,13 +799,13 @@ const TOOL_DEFINITIONS = [
714
799
  category: "symbols",
715
800
  searchHint: "batch get multiple symbols by IDs",
716
801
  description: "Retrieve multiple symbols by ID in a single batch call",
717
- schema: {
802
+ schema: lazySchema(() => ({
718
803
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
719
804
  symbol_ids: z.union([
720
805
  z.array(z.string()),
721
806
  z.string().transform((s) => JSON.parse(s)),
722
807
  ]).describe("Array of symbol identifiers. Can be passed as JSON string."),
723
- },
808
+ })),
724
809
  handler: async (args) => {
725
810
  const syms = await getSymbols(args.repo, args.symbol_ids);
726
811
  const output = await formatSymbolsCompact(syms);
@@ -733,11 +818,11 @@ const TOOL_DEFINITIONS = [
733
818
  category: "symbols",
734
819
  searchHint: "find symbol by name show source code references",
735
820
  description: "Find a symbol by name and show its source, optionally including references",
736
- schema: {
821
+ schema: lazySchema(() => ({
737
822
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
738
823
  query: z.string().describe("Symbol name or query to search for"),
739
824
  include_refs: zBool().describe("Include locations that reference this symbol"),
740
- },
825
+ })),
741
826
  handler: async (args) => {
742
827
  const result = await findAndShow(args.repo, args.query, args.include_refs);
743
828
  if (!result)
@@ -754,10 +839,10 @@ const TOOL_DEFINITIONS = [
754
839
  category: "symbols",
755
840
  searchHint: "context bundle symbol imports siblings callers one call",
756
841
  description: "Symbol + imports + siblings in one call. Saves 2-3 round-trips.",
757
- schema: {
842
+ schema: lazySchema(() => ({
758
843
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
759
844
  symbol_name: z.string().describe("Symbol name to find"),
760
- },
845
+ })),
761
846
  handler: async (args) => {
762
847
  const bundle = await getContextBundle(args.repo, args.symbol_name);
763
848
  if (!bundle)
@@ -772,13 +857,13 @@ const TOOL_DEFINITIONS = [
772
857
  searchHint: "find references usages callers who uses symbol",
773
858
  outputSchema: OutputSchemas.references,
774
859
  description: "Find all references to a symbol. Pass symbol_names array for batch search.",
775
- schema: {
860
+ schema: lazySchema(() => ({
776
861
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
777
862
  symbol_name: z.string().optional().describe("Name of the symbol to find references for"),
778
863
  symbol_names: z.union([z.array(z.string()), z.string().transform((s) => JSON.parse(s))]).optional()
779
864
  .describe("Array of symbol names for batch search (reads each file once). Can be JSON string."),
780
865
  file_pattern: z.string().optional().describe("Glob pattern to filter files"),
781
- },
866
+ })),
782
867
  handler: async (args) => {
783
868
  const names = args.symbol_names;
784
869
  if (names && names.length > 0) {
@@ -796,7 +881,7 @@ const TOOL_DEFINITIONS = [
796
881
  searchHint: "trace call chain callers callees dependency graph mermaid react hooks",
797
882
  outputSchema: OutputSchemas.callTree,
798
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.",
799
- schema: {
884
+ schema: lazySchema(() => ({
800
885
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
801
886
  symbol_name: z.string().describe("Name of the symbol to trace"),
802
887
  direction: z.enum(["callers", "callees"]).describe("Trace direction"),
@@ -805,7 +890,7 @@ const TOOL_DEFINITIONS = [
805
890
  include_tests: zBool().describe("Include test files in trace results (default: false)"),
806
891
  output_format: z.enum(["json", "mermaid"]).optional().describe("Output format: 'json' (default) or 'mermaid' (flowchart diagram)"),
807
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)"),
808
- },
893
+ })),
809
894
  handler: async (args) => {
810
895
  const result = await traceCallChain(args.repo, args.symbol_name, args.direction, {
811
896
  depth: args.depth,
@@ -826,13 +911,13 @@ const TOOL_DEFINITIONS = [
826
911
  searchHint: "impact analysis blast radius git changes affected symbols",
827
912
  outputSchema: OutputSchemas.impactAnalysis,
828
913
  description: "Blast radius of git changes — affected symbols and files.",
829
- schema: {
914
+ schema: lazySchema(() => ({
830
915
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
831
916
  since: z.string().describe("Git ref to compare from (e.g. HEAD~3, commit SHA, branch)"),
832
917
  depth: zNum().describe("Depth of dependency traversal"),
833
918
  until: z.string().optional().describe("Git ref to compare to (defaults to HEAD)"),
834
919
  include_source: zBool().describe("Include full source code of affected symbols (default: false)"),
835
- },
920
+ })),
836
921
  handler: async (args) => {
837
922
  const result = await impactAnalysis(args.repo, args.since, {
838
923
  depth: args.depth,
@@ -847,14 +932,14 @@ const TOOL_DEFINITIONS = [
847
932
  category: "graph",
848
933
  searchHint: "react component tree composition render jsx parent child hierarchy",
849
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.",
850
- schema: {
935
+ schema: lazySchema(() => ({
851
936
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
852
937
  component_name: z.string().describe("Root component name (must have kind 'component' in index)"),
853
938
  depth: zNum().describe("Maximum depth of composition tree (default: 3)"),
854
939
  include_source: zBool().describe("Include full source of each component (default: false)"),
855
940
  include_tests: zBool().describe("Include test files (default: false)"),
856
941
  output_format: z.enum(["json", "mermaid"]).optional().describe("Output format: 'json' (default) or 'mermaid'"),
857
- },
942
+ })),
858
943
  handler: async (args) => {
859
944
  const result = await traceComponentTree(args.repo, args.component_name, {
860
945
  depth: args.depth,
@@ -870,13 +955,13 @@ const TOOL_DEFINITIONS = [
870
955
  category: "analysis",
871
956
  searchHint: "react hooks analyze inventory rule of hooks violations usestate useeffect custom",
872
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.",
873
- schema: {
958
+ schema: lazySchema(() => ({
874
959
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
875
960
  component_name: z.string().optional().describe("Filter to single component/hook (default: all)"),
876
961
  file_pattern: z.string().optional().describe("Filter by file path substring"),
877
962
  include_tests: zBool().describe("Include test files (default: false)"),
878
963
  max_entries: zNum().describe("Max entries to return (default: 100)"),
879
- },
964
+ })),
880
965
  handler: async (args) => {
881
966
  const result = await analyzeHooks(args.repo, {
882
967
  component_name: args.component_name,
@@ -892,13 +977,13 @@ const TOOL_DEFINITIONS = [
892
977
  category: "analysis",
893
978
  searchHint: "react render performance inline props memo useCallback useMemo re-render risk optimization",
894
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.",
895
- schema: {
980
+ schema: lazySchema(() => ({
896
981
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
897
982
  component_name: z.string().optional().describe("Filter to single component (default: all)"),
898
983
  file_pattern: z.string().optional().describe("Filter by file path substring"),
899
984
  include_tests: zBool().describe("Include test files (default: false)"),
900
985
  max_entries: zNum().describe("Max entries to return (default: 100)"),
901
- },
986
+ })),
902
987
  handler: async (args) => {
903
988
  const result = await analyzeRenders(args.repo, {
904
989
  component_name: args.component_name,
@@ -914,9 +999,9 @@ const TOOL_DEFINITIONS = [
914
999
  category: "analysis",
915
1000
  searchHint: "react context createContext provider useContext consumer re-render propagation",
916
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.",
917
- schema: {
1002
+ schema: lazySchema(() => ({
918
1003
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
919
- },
1004
+ })),
920
1005
  handler: async (args) => {
921
1006
  const index = await getCodeIndex(args.repo);
922
1007
  if (!index)
@@ -930,11 +1015,11 @@ const TOOL_DEFINITIONS = [
930
1015
  category: "analysis",
931
1016
  searchHint: "react compiler forget memoization bailout readiness migration adoption auto-memo",
932
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.",
933
- schema: {
1018
+ schema: lazySchema(() => ({
934
1019
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
935
1020
  file_pattern: z.string().optional().describe("Filter by file path substring"),
936
1021
  include_tests: zBool().describe("Include test files (default: false)"),
937
- },
1022
+ })),
938
1023
  handler: async (args) => {
939
1024
  const result = await auditCompilerReadiness(args.repo, {
940
1025
  file_pattern: args.file_pattern,
@@ -948,9 +1033,9 @@ const TOOL_DEFINITIONS = [
948
1033
  category: "analysis",
949
1034
  searchHint: "react onboarding day-1 overview stack inventory components hooks critical issues",
950
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.",
951
- schema: {
1036
+ schema: lazySchema(() => ({
952
1037
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
953
- },
1038
+ })),
954
1039
  handler: async (args) => {
955
1040
  const result = await reactQuickstart(args.repo);
956
1041
  return JSON.stringify(result, null, 2);
@@ -961,11 +1046,11 @@ const TOOL_DEFINITIONS = [
961
1046
  category: "graph",
962
1047
  searchHint: "trace HTTP route handler API endpoint service database NestJS Express Next.js",
963
1048
  description: "Trace HTTP route → handler → service → DB. NestJS, Next.js, Express.",
964
- schema: {
1049
+ schema: lazySchema(() => ({
965
1050
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
966
1051
  path: z.string().describe("URL path to trace (e.g. '/api/users', '/api/projects/:id')"),
967
1052
  output_format: z.enum(["json", "mermaid"]).optional().describe("Output format: 'json' (default) or 'mermaid' (sequence diagram)"),
968
- },
1053
+ })),
969
1054
  handler: async (args) => {
970
1055
  const result = await traceRoute(args.repo, args.path, args.output_format);
971
1056
  return formatTraceRoute(result);
@@ -977,13 +1062,13 @@ const TOOL_DEFINITIONS = [
977
1062
  searchHint: "go to definition jump navigate LSP language server",
978
1063
  outputSchema: OutputSchemas.definition,
979
1064
  description: "Go to the definition of a symbol. Uses LSP when available for type-safe precision, falls back to index search.",
980
- schema: {
1065
+ schema: lazySchema(() => ({
981
1066
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
982
1067
  symbol_name: z.string().describe("Symbol name to find definition of"),
983
1068
  file_path: z.string().optional().describe("File containing the symbol reference (for LSP precision)"),
984
1069
  line: zNum().describe("0-based line number of the reference"),
985
1070
  character: zNum().describe("0-based column of the reference"),
986
- },
1071
+ })),
987
1072
  handler: async (args) => {
988
1073
  const result = await goToDefinition(args.repo, args.symbol_name, args.file_path, args.line, args.character);
989
1074
  if (!result)
@@ -998,13 +1083,13 @@ const TOOL_DEFINITIONS = [
998
1083
  searchHint: "type information hover documentation return type parameters LSP",
999
1084
  outputSchema: OutputSchemas.typeInfo,
1000
1085
  description: "Get type info via LSP hover (return type, params, docs). Hint if LSP unavailable.",
1001
- schema: {
1086
+ schema: lazySchema(() => ({
1002
1087
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1003
1088
  symbol_name: z.string().describe("Symbol name to get type info for"),
1004
1089
  file_path: z.string().optional().describe("File containing the symbol"),
1005
1090
  line: zNum().describe("0-based line number"),
1006
1091
  character: zNum().describe("0-based column"),
1007
- },
1092
+ })),
1008
1093
  handler: (args) => getTypeInfo(args.repo, args.symbol_name, args.file_path, args.line, args.character),
1009
1094
  },
1010
1095
  {
@@ -1013,14 +1098,14 @@ const TOOL_DEFINITIONS = [
1013
1098
  searchHint: "rename symbol refactor LSP type-safe all files",
1014
1099
  outputSchema: OutputSchemas.renameResult,
1015
1100
  description: "Rename symbol across all files via LSP. Type-safe, updates imports/refs.",
1016
- schema: {
1101
+ schema: lazySchema(() => ({
1017
1102
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1018
1103
  symbol_name: z.string().describe("Current name of the symbol to rename"),
1019
1104
  new_name: z.string().describe("New name for the symbol"),
1020
1105
  file_path: z.string().optional().describe("File containing the symbol"),
1021
1106
  line: zNum().describe("0-based line number"),
1022
1107
  character: zNum().describe("0-based column"),
1023
- },
1108
+ })),
1024
1109
  handler: (args) => renameSymbol(args.repo, args.symbol_name, args.new_name, args.file_path, args.line, args.character),
1025
1110
  },
1026
1111
  {
@@ -1029,13 +1114,13 @@ const TOOL_DEFINITIONS = [
1029
1114
  searchHint: "call hierarchy incoming outgoing calls who calls what calls LSP callers callees",
1030
1115
  outputSchema: OutputSchemas.callHierarchy,
1031
1116
  description: "LSP call hierarchy: incoming + outgoing calls. Complements trace_call_chain.",
1032
- schema: {
1117
+ schema: lazySchema(() => ({
1033
1118
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1034
1119
  symbol_name: z.string().describe("Symbol name to get call hierarchy for"),
1035
1120
  file_path: z.string().optional().describe("File containing the symbol (for LSP precision)"),
1036
1121
  line: zNum().describe("0-based line number"),
1037
1122
  character: zNum().describe("0-based column"),
1038
- },
1123
+ })),
1039
1124
  handler: async (args) => {
1040
1125
  const result = await getCallHierarchy(args.repo, args.symbol_name, args.file_path, args.line, args.character);
1041
1126
  if (result.via === "unavailable") {
@@ -1067,12 +1152,12 @@ const TOOL_DEFINITIONS = [
1067
1152
  category: "architecture",
1068
1153
  searchHint: "community detection clusters modules Louvain import graph boundaries",
1069
1154
  description: "Louvain community detection on import graph. Discovers module boundaries.",
1070
- schema: {
1155
+ schema: lazySchema(() => ({
1071
1156
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1072
1157
  focus: z.string().optional().describe("Path substring to filter files (e.g. 'src/lib')"),
1073
1158
  resolution: zNum().describe("Louvain resolution: higher = more smaller communities, lower = fewer larger (default: 1.0)"),
1074
1159
  output_format: z.enum(["json", "mermaid"]).optional().describe("Output format: 'json' (default) or 'mermaid' (graph diagram)"),
1075
- },
1160
+ })),
1076
1161
  handler: async (args) => {
1077
1162
  const result = await detectCommunities(args.repo, args.focus, args.resolution, args.output_format);
1078
1163
  return formatCommunities(result);
@@ -1083,11 +1168,11 @@ const TOOL_DEFINITIONS = [
1083
1168
  category: "architecture",
1084
1169
  searchHint: "circular dependency cycle import loop detection",
1085
1170
  description: "Detect circular dependencies in the import graph via DFS. Returns file-level cycles.",
1086
- schema: {
1171
+ schema: lazySchema(() => ({
1087
1172
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1088
1173
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
1089
1174
  max_cycles: zNum().describe("Maximum cycles to report (default: 50)"),
1090
- },
1175
+ })),
1091
1176
  handler: async (args) => {
1092
1177
  const { findCircularDeps } = await import("./tools/graph-tools.js");
1093
1178
  const opts = {};
@@ -1111,7 +1196,7 @@ const TOOL_DEFINITIONS = [
1111
1196
  category: "architecture",
1112
1197
  searchHint: "boundary rules architecture enforcement imports CI gate hexagonal onion",
1113
1198
  description: "Check architecture boundary rules against imports. Path substring matching.",
1114
- schema: {
1199
+ schema: lazySchema(() => ({
1115
1200
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1116
1201
  rules: z.union([
1117
1202
  z.array(z.object({
@@ -1122,7 +1207,7 @@ const TOOL_DEFINITIONS = [
1122
1207
  z.string().transform((s) => JSON.parse(s)),
1123
1208
  ]).describe("Array of boundary rules to check. JSON string OK."),
1124
1209
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
1125
- },
1210
+ })),
1126
1211
  handler: async (args) => {
1127
1212
  const { checkBoundaries } = await import("./tools/boundary-tools.js");
1128
1213
  return checkBoundaries(args.repo, args.rules, { file_pattern: args.file_pattern });
@@ -1133,12 +1218,12 @@ const TOOL_DEFINITIONS = [
1133
1218
  category: "architecture",
1134
1219
  searchHint: "classify roles entry core utility dead leaf symbol architecture",
1135
1220
  description: "Classify symbol roles (entry/core/utility/dead/leaf) by call graph connectivity.",
1136
- schema: {
1221
+ schema: lazySchema(() => ({
1137
1222
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1138
1223
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
1139
1224
  include_tests: zBool().describe("Include test files (default: false)"),
1140
1225
  top_n: zNum().describe("Maximum number of symbols to return (default: 100)"),
1141
- },
1226
+ })),
1142
1227
  handler: async (args) => {
1143
1228
  const { classifySymbolRoles } = await import("./tools/graph-tools.js");
1144
1229
  const result = await classifySymbolRoles(args.repo, {
@@ -1155,13 +1240,13 @@ const TOOL_DEFINITIONS = [
1155
1240
  category: "context",
1156
1241
  searchHint: "assemble context token budget L0 L1 L2 L3 source signatures summaries",
1157
1242
  description: "Assemble code context within token budget. L0=source, L1=signatures, L2=files, L3=dirs.",
1158
- schema: {
1243
+ schema: lazySchema(() => ({
1159
1244
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1160
1245
  query: z.string().describe("Natural language query describing what context is needed"),
1161
1246
  token_budget: zNum().describe("Maximum tokens for the assembled context"),
1162
1247
  level: z.enum(["L0", "L1", "L2", "L3"]).optional().describe("L0=source (default), L1=signatures, L2=files, L3=dirs"),
1163
1248
  rerank: zBool().describe("Rerank results using cross-encoder model for improved relevance (requires @huggingface/transformers)"),
1164
- },
1249
+ })),
1165
1250
  handler: async (args) => {
1166
1251
  const result = await assembleContext(args.repo, args.query, args.token_budget, args.level, args.rerank);
1167
1252
  return formatAssembleContext(result);
@@ -1172,12 +1257,12 @@ const TOOL_DEFINITIONS = [
1172
1257
  category: "context",
1173
1258
  searchHint: "knowledge map module dependency graph architecture overview mermaid",
1174
1259
  description: "Get the module dependency map showing how files and directories relate",
1175
- schema: {
1260
+ schema: lazySchema(() => ({
1176
1261
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1177
1262
  focus: z.string().optional().describe("Focus on a specific module or directory"),
1178
1263
  depth: zNum().describe("Maximum depth of the dependency graph"),
1179
1264
  output_format: z.enum(["json", "mermaid"]).optional().describe("Output format: 'json' (default) or 'mermaid' (dependency diagram)"),
1180
- },
1265
+ })),
1181
1266
  handler: async (args) => {
1182
1267
  const result = await getKnowledgeMap(args.repo, args.focus, args.depth, args.output_format);
1183
1268
  return formatKnowledgeMap(result);
@@ -1189,11 +1274,11 @@ const TOOL_DEFINITIONS = [
1189
1274
  category: "diff",
1190
1275
  searchHint: "diff outline structural changes git refs compare",
1191
1276
  description: "Get a structural outline of what changed between two git refs",
1192
- schema: {
1277
+ schema: lazySchema(() => ({
1193
1278
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1194
1279
  since: z.string().describe("Git ref to compare from"),
1195
1280
  until: z.string().optional().describe("Git ref to compare to (defaults to HEAD)"),
1196
- },
1281
+ })),
1197
1282
  handler: async (args) => {
1198
1283
  const result = await diffOutline(args.repo, args.since, args.until);
1199
1284
  return formatDiffOutline(result);
@@ -1204,12 +1289,12 @@ const TOOL_DEFINITIONS = [
1204
1289
  category: "diff",
1205
1290
  searchHint: "changed symbols added modified removed git diff",
1206
1291
  description: "List symbols that were added, modified, or removed between two git refs",
1207
- schema: {
1292
+ schema: lazySchema(() => ({
1208
1293
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1209
1294
  since: z.string().describe("Git ref to compare from"),
1210
1295
  until: z.string().optional().describe("Git ref to compare to (defaults to HEAD)"),
1211
1296
  include_diff: zBool().describe("Include unified diff per changed file (truncated to 500 chars)"),
1212
- },
1297
+ })),
1213
1298
  handler: async (args) => {
1214
1299
  const opts = {};
1215
1300
  if (args.include_diff === true)
@@ -1224,10 +1309,10 @@ const TOOL_DEFINITIONS = [
1224
1309
  category: "reporting",
1225
1310
  searchHint: "generate CLAUDE.md project summary documentation",
1226
1311
  description: "Generate a CLAUDE.md project summary file from the repository index",
1227
- schema: {
1312
+ schema: lazySchema(() => ({
1228
1313
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1229
1314
  output_path: z.string().optional().describe("Custom output file path"),
1230
- },
1315
+ })),
1231
1316
  handler: (args) => generateClaudeMd(args.repo, args.output_path),
1232
1317
  },
1233
1318
  // --- Batch retrieval ---
@@ -1237,7 +1322,7 @@ const TOOL_DEFINITIONS = [
1237
1322
  searchHint: "batch retrieval multi-query semantic hybrid token budget",
1238
1323
  outputSchema: OutputSchemas.batchResults,
1239
1324
  description: "Batch multi-query retrieval with shared token budget. Supports symbols/text/semantic/hybrid.",
1240
- schema: {
1325
+ schema: lazySchema(() => ({
1241
1326
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1242
1327
  queries: z
1243
1328
  .union([
@@ -1246,7 +1331,7 @@ const TOOL_DEFINITIONS = [
1246
1331
  ])
1247
1332
  .describe("Sub-queries array (symbols/text/file_tree/outline/references/call_chain/impact/context/knowledge_map). JSON string OK."),
1248
1333
  token_budget: zNum().describe("Maximum total tokens across all sub-query results"),
1249
- },
1334
+ })),
1250
1335
  handler: async (args) => {
1251
1336
  const result = await codebaseRetrieval(args.repo, args.queries, args.token_budget);
1252
1337
  // Format as text sections instead of JSON envelope
@@ -1267,11 +1352,11 @@ const TOOL_DEFINITIONS = [
1267
1352
  searchHint: "dead code unused exports unreferenced symbols cleanup",
1268
1353
  outputSchema: OutputSchemas.deadCode,
1269
1354
  description: "Find dead code: exported symbols with zero external references.",
1270
- schema: {
1355
+ schema: lazySchema(() => ({
1271
1356
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1272
1357
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
1273
1358
  include_tests: zBool().describe("Include test files in scan (default: false)"),
1274
- },
1359
+ })),
1275
1360
  handler: async (args) => {
1276
1361
  const result = await findDeadCode(args.repo, {
1277
1362
  file_pattern: args.file_pattern,
@@ -1288,11 +1373,11 @@ const TOOL_DEFINITIONS = [
1288
1373
  category: "analysis",
1289
1374
  searchHint: "unused imports dead cleanup lint",
1290
1375
  description: "Find imported names never referenced in the file body. Complements find_dead_code.",
1291
- schema: {
1376
+ schema: lazySchema(() => ({
1292
1377
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1293
1378
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
1294
1379
  include_tests: zBool().describe("Include test files in scan (default: false)"),
1295
- },
1380
+ })),
1296
1381
  handler: async (args) => {
1297
1382
  const { findUnusedImports } = await import("./tools/symbol-tools.js");
1298
1383
  const opts = {};
@@ -1317,13 +1402,13 @@ const TOOL_DEFINITIONS = [
1317
1402
  searchHint: "complexity cyclomatic nesting refactoring functions",
1318
1403
  outputSchema: OutputSchemas.complexity,
1319
1404
  description: "Top N most complex functions by cyclomatic complexity, nesting, lines.",
1320
- schema: {
1405
+ schema: lazySchema(() => ({
1321
1406
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1322
1407
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
1323
1408
  top_n: zNum().describe("Return top N most complex functions (default: 30)"),
1324
1409
  min_complexity: zNum().describe("Minimum cyclomatic complexity to include (default: 1)"),
1325
1410
  include_tests: zBool().describe("Include test files (default: false)"),
1326
- },
1411
+ })),
1327
1412
  handler: async (args) => {
1328
1413
  const result = await analyzeComplexity(args.repo, {
1329
1414
  file_pattern: args.file_pattern,
@@ -1343,13 +1428,13 @@ const TOOL_DEFINITIONS = [
1343
1428
  searchHint: "code clones duplicates copy-paste detection similar functions",
1344
1429
  outputSchema: OutputSchemas.clones,
1345
1430
  description: "Find code clones: similar function pairs via hash bucketing + line-similarity.",
1346
- schema: {
1431
+ schema: lazySchema(() => ({
1347
1432
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1348
1433
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
1349
1434
  min_similarity: zNum().describe("Minimum similarity threshold 0-1 (default: 0.7)"),
1350
1435
  min_lines: zNum().describe("Minimum normalized lines to consider (default: 10)"),
1351
1436
  include_tests: zBool().describe("Include test files (default: false)"),
1352
- },
1437
+ })),
1353
1438
  handler: async (args) => {
1354
1439
  const result = await findClones(args.repo, {
1355
1440
  file_pattern: args.file_pattern,
@@ -1365,7 +1450,7 @@ const TOOL_DEFINITIONS = [
1365
1450
  category: "analysis",
1366
1451
  searchHint: "frequency analysis common patterns AST shape clusters",
1367
1452
  description: "Group functions by normalized AST shape. Finds emergent patterns invisible to regex.",
1368
- schema: {
1453
+ schema: lazySchema(() => ({
1369
1454
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1370
1455
  top_n: zNum().optional().describe("Number of clusters to return (default: 30)"),
1371
1456
  min_nodes: zNum().optional().describe("Minimum AST nodes in a subtree to include (default: 5)"),
@@ -1373,7 +1458,7 @@ const TOOL_DEFINITIONS = [
1373
1458
  kind: z.string().optional().describe("Filter by symbol kind, comma-separated (default: function,method)"),
1374
1459
  include_tests: zBool().describe("Include test files (default: false)"),
1375
1460
  token_budget: zNum().optional().describe("Max tokens for response"),
1376
- },
1461
+ })),
1377
1462
  handler: async (args) => frequencyAnalysis(args.repo, {
1378
1463
  top_n: args.top_n,
1379
1464
  min_nodes: args.min_nodes,
@@ -1388,12 +1473,12 @@ const TOOL_DEFINITIONS = [
1388
1473
  category: "analysis",
1389
1474
  searchHint: "hotspots git churn bug-prone change frequency complexity",
1390
1475
  description: "Git churn hotspots: change frequency × complexity. Higher score = more bug-prone.",
1391
- schema: {
1476
+ schema: lazySchema(() => ({
1392
1477
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1393
1478
  since_days: zNum().describe("Look back N days (default: 90)"),
1394
1479
  top_n: zNum().describe("Return top N hotspots (default: 30)"),
1395
1480
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
1396
- },
1481
+ })),
1397
1482
  handler: async (args) => {
1398
1483
  const result = await analyzeHotspots(args.repo, {
1399
1484
  since_days: args.since_days,
@@ -1409,13 +1494,13 @@ const TOOL_DEFINITIONS = [
1409
1494
  category: "cross-repo",
1410
1495
  searchHint: "cross-repo search symbols across all repositories monorepo microservice",
1411
1496
  description: "Search symbols across ALL indexed repositories. Useful for monorepos and microservice architectures.",
1412
- schema: {
1497
+ schema: lazySchema(() => ({
1413
1498
  query: z.string().describe("Symbol search query"),
1414
1499
  repo_pattern: z.string().optional().describe("Filter repos by name pattern (e.g. 'local/tgm')"),
1415
1500
  kind: z.string().optional().describe("Filter by symbol kind"),
1416
1501
  top_k: zNum().describe("Max results per repo (default: 10)"),
1417
1502
  include_source: zBool().describe("Include source code"),
1418
- },
1503
+ })),
1419
1504
  handler: (args) => crossRepoSearchSymbols(args.query, {
1420
1505
  repo_pattern: args.repo_pattern,
1421
1506
  kind: args.kind,
@@ -1428,11 +1513,11 @@ const TOOL_DEFINITIONS = [
1428
1513
  category: "cross-repo",
1429
1514
  searchHint: "cross-repo references symbol across all repositories",
1430
1515
  description: "Find references to a symbol across ALL indexed repositories.",
1431
- schema: {
1516
+ schema: lazySchema(() => ({
1432
1517
  symbol_name: z.string().describe("Symbol name to find references for"),
1433
1518
  repo_pattern: z.string().optional().describe("Filter repos by name pattern"),
1434
1519
  file_pattern: z.string().optional().describe("Filter files by glob pattern"),
1435
- },
1520
+ })),
1436
1521
  handler: (args) => crossRepoFindReferences(args.symbol_name, {
1437
1522
  repo_pattern: args.repo_pattern,
1438
1523
  file_pattern: args.file_pattern,
@@ -1444,13 +1529,13 @@ const TOOL_DEFINITIONS = [
1444
1529
  category: "patterns",
1445
1530
  searchHint: "search patterns anti-patterns CQ violations useEffect empty-catch console-log",
1446
1531
  description: "Search structural patterns/anti-patterns. Built-in or custom regex.",
1447
- schema: {
1532
+ schema: lazySchema(() => ({
1448
1533
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1449
1534
  pattern: z.string().describe("Built-in pattern name or custom regex"),
1450
1535
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
1451
1536
  include_tests: zBool().describe("Include test files (default: false)"),
1452
1537
  max_results: zNum().describe("Max results (default: 50)"),
1453
- },
1538
+ })),
1454
1539
  handler: async (args) => {
1455
1540
  const result = await searchPatterns(args.repo, args.pattern, {
1456
1541
  file_pattern: args.file_pattern,
@@ -1465,7 +1550,7 @@ const TOOL_DEFINITIONS = [
1465
1550
  category: "patterns",
1466
1551
  searchHint: "list available built-in patterns anti-patterns",
1467
1552
  description: "List all available built-in structural code patterns for search_patterns.",
1468
- schema: {},
1553
+ schema: lazySchema(() => ({})),
1469
1554
  handler: async () => listPatterns(),
1470
1555
  },
1471
1556
  // --- Report ---
@@ -1474,9 +1559,9 @@ const TOOL_DEFINITIONS = [
1474
1559
  category: "reporting",
1475
1560
  searchHint: "generate HTML report complexity dead code hotspots architecture browser",
1476
1561
  description: "Generate a standalone HTML report with complexity, dead code, hotspots, and architecture. Opens in any browser.",
1477
- schema: {
1562
+ schema: lazySchema(() => ({
1478
1563
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1479
- },
1564
+ })),
1480
1565
  handler: (args) => generateReport(args.repo),
1481
1566
  },
1482
1567
  // --- Conversations ---
@@ -1485,10 +1570,10 @@ const TOOL_DEFINITIONS = [
1485
1570
  category: "conversations",
1486
1571
  searchHint: "index conversations Claude Code history JSONL",
1487
1572
  description: "Index Claude Code conversation history for search. Scans JSONL files in ~/.claude/projects/ for the given project path.",
1488
- schema: {
1573
+ schema: lazySchema(() => ({
1489
1574
  project_path: z.string().optional().describe("Path to the Claude project conversations directory. Auto-detects from cwd if omitted."),
1490
1575
  quiet: zBool().describe("Suppress output (used by session-end hook)"),
1491
- },
1576
+ })),
1492
1577
  handler: async (args) => indexConversations(args.project_path),
1493
1578
  },
1494
1579
  {
@@ -1496,11 +1581,11 @@ const TOOL_DEFINITIONS = [
1496
1581
  category: "conversations",
1497
1582
  searchHint: "search conversations past sessions history BM25 semantic",
1498
1583
  description: "Search conversations in one project (BM25+semantic). For all projects: search_all_conversations.",
1499
- schema: {
1584
+ schema: lazySchema(() => ({
1500
1585
  query: z.string().describe("Search query — keywords or natural language"),
1501
1586
  project: z.string().optional().describe("Project path to search (default: current project)"),
1502
1587
  limit: zNum().optional().describe("Maximum results to return (default: 10, max: 50)"),
1503
- },
1588
+ })),
1504
1589
  handler: async (args) => {
1505
1590
  const result = await searchConversations(args.query, args.project, args.limit);
1506
1591
  return formatConversations(result);
@@ -1511,11 +1596,11 @@ const TOOL_DEFINITIONS = [
1511
1596
  category: "conversations",
1512
1597
  searchHint: "find conversations symbol discussion cross-reference code",
1513
1598
  description: "Find conversations that discussed a code symbol. Cross-refs code + history.",
1514
- schema: {
1599
+ schema: lazySchema(() => ({
1515
1600
  symbol_name: z.string().describe("Name of the code symbol to search for in conversations"),
1516
1601
  repo: z.string().describe("Code repository to resolve the symbol from (e.g., 'local/my-project')"),
1517
1602
  limit: zNum().optional().describe("Maximum conversation results (default: 5)"),
1518
- },
1603
+ })),
1519
1604
  handler: async (args) => {
1520
1605
  const result = await findConversationsForSymbol(args.symbol_name, args.repo, args.limit);
1521
1606
  return formatConversations(result);
@@ -1526,10 +1611,10 @@ const TOOL_DEFINITIONS = [
1526
1611
  category: "conversations",
1527
1612
  searchHint: "search all conversations every project cross-project",
1528
1613
  description: "Search ALL conversation projects at once, ranked by relevance.",
1529
- schema: {
1614
+ schema: lazySchema(() => ({
1530
1615
  query: z.string().describe("Search query — keywords, natural language, or concept"),
1531
1616
  limit: zNum().optional().describe("Maximum results across all projects (default: 10)"),
1532
- },
1617
+ })),
1533
1618
  handler: async (args) => {
1534
1619
  const result = await searchAllConversations(args.query, args.limit);
1535
1620
  return formatConversations(result);
@@ -1542,13 +1627,13 @@ const TOOL_DEFINITIONS = [
1542
1627
  searchHint: "scan secrets API keys tokens passwords credentials security",
1543
1628
  outputSchema: OutputSchemas.secrets,
1544
1629
  description: "Scan for hardcoded secrets (API keys, tokens, passwords). ~1,100 rules.",
1545
- schema: {
1630
+ schema: lazySchema(() => ({
1546
1631
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1547
1632
  file_pattern: z.string().optional().describe("Glob pattern to filter scanned files"),
1548
1633
  min_confidence: z.enum(["high", "medium", "low"]).optional().describe("Minimum confidence level (default: medium)"),
1549
1634
  exclude_tests: zBool().describe("Exclude test file findings (default: true)"),
1550
1635
  severity: z.enum(["critical", "high", "medium", "low"]).optional().describe("Minimum severity level"),
1551
- },
1636
+ })),
1552
1637
  handler: async (args) => {
1553
1638
  const result = await scanSecrets(args.repo, {
1554
1639
  file_pattern: args.file_pattern,
@@ -1566,11 +1651,11 @@ const TOOL_DEFINITIONS = [
1566
1651
  requiresLanguage: "kotlin",
1567
1652
  searchHint: "kotlin extension function receiver type method discovery",
1568
1653
  description: "Find all Kotlin extension functions for a given receiver type. Scans indexed symbols for signatures matching 'ReceiverType.' prefix.",
1569
- schema: {
1654
+ schema: lazySchema(() => ({
1570
1655
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1571
1656
  receiver_type: z.string().describe("Receiver type name, e.g. 'String', 'List', 'User'"),
1572
1657
  file_pattern: z.string().optional().describe("Filter by file path substring"),
1573
- },
1658
+ })),
1574
1659
  handler: async (args) => {
1575
1660
  const opts = {};
1576
1661
  if (typeof args.file_pattern === "string")
@@ -1584,10 +1669,10 @@ const TOOL_DEFINITIONS = [
1584
1669
  requiresLanguage: "kotlin",
1585
1670
  searchHint: "kotlin sealed class interface subtype when exhaustive branch missing hierarchy",
1586
1671
  description: "Analyze a Kotlin sealed class/interface: find all subtypes and check when() blocks for exhaustiveness (missing branches).",
1587
- schema: {
1672
+ schema: lazySchema(() => ({
1588
1673
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1589
1674
  sealed_class: z.string().describe("Name of the sealed class or interface to analyze"),
1590
- },
1675
+ })),
1591
1676
  handler: async (args) => {
1592
1677
  return await analyzeSealedHierarchy(args.repo, args.sealed_class);
1593
1678
  },
@@ -1597,11 +1682,11 @@ const TOOL_DEFINITIONS = [
1597
1682
  category: "analysis",
1598
1683
  searchHint: "hilt dagger DI dependency injection viewmodel inject module provides binds android kotlin graph",
1599
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.",
1600
- schema: {
1685
+ schema: lazySchema(() => ({
1601
1686
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1602
1687
  class_name: z.string().describe("Name of the Hilt-annotated class (e.g. 'UserViewModel')"),
1603
1688
  depth: z.number().optional().describe("Max traversal depth (default: 1)"),
1604
- },
1689
+ })),
1605
1690
  handler: async (args) => {
1606
1691
  const opts = {};
1607
1692
  if (typeof args.depth === "number")
@@ -1614,11 +1699,11 @@ const TOOL_DEFINITIONS = [
1614
1699
  category: "analysis",
1615
1700
  searchHint: "kotlin coroutine suspend dispatcher withContext runBlocking Thread.sleep blocking chain trace anti-pattern",
1616
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.",
1617
- schema: {
1702
+ schema: lazySchema(() => ({
1618
1703
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1619
1704
  function_name: z.string().describe("Name of the suspend function to trace"),
1620
1705
  depth: z.number().optional().describe("Max chain depth (default: 3)"),
1621
- },
1706
+ })),
1622
1707
  handler: async (args) => {
1623
1708
  const opts = {};
1624
1709
  if (typeof args.depth === "number")
@@ -1631,9 +1716,9 @@ const TOOL_DEFINITIONS = [
1631
1716
  category: "analysis",
1632
1717
  searchHint: "kotlin multiplatform kmp expect actual source set common main android ios jvm js missing orphan",
1633
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.",
1634
- schema: {
1719
+ schema: lazySchema(() => ({
1635
1720
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1636
- },
1721
+ })),
1637
1722
  handler: async (args) => {
1638
1723
  return await analyzeKmpDeclarations(args.repo);
1639
1724
  },
@@ -1644,11 +1729,11 @@ const TOOL_DEFINITIONS = [
1644
1729
  category: "analysis",
1645
1730
  searchHint: "kotlin compose composable component tree hierarchy ui call graph jetpack preview",
1646
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.",
1647
- schema: {
1732
+ schema: lazySchema(() => ({
1648
1733
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1649
1734
  root_name: z.string().describe("Name of the root @Composable function (e.g. 'HomeScreen')"),
1650
1735
  depth: z.number().optional().describe("Max tree depth (default: 10)"),
1651
- },
1736
+ })),
1652
1737
  handler: async (args) => {
1653
1738
  const opts = {};
1654
1739
  if (typeof args.depth === "number")
@@ -1661,10 +1746,10 @@ const TOOL_DEFINITIONS = [
1661
1746
  category: "analysis",
1662
1747
  searchHint: "kotlin compose recomposition unstable remember mutableStateOf performance skip lambda collection",
1663
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.",
1664
- schema: {
1749
+ schema: lazySchema(() => ({
1665
1750
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1666
1751
  file_pattern: z.string().optional().describe("Filter by file path substring"),
1667
- },
1752
+ })),
1668
1753
  handler: async (args) => {
1669
1754
  const opts = {};
1670
1755
  if (typeof args.file_pattern === "string")
@@ -1677,9 +1762,9 @@ const TOOL_DEFINITIONS = [
1677
1762
  category: "analysis",
1678
1763
  searchHint: "kotlin room database entity dao query insert update delete schema sqlite persistence android",
1679
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.",
1680
- schema: {
1765
+ schema: lazySchema(() => ({
1681
1766
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1682
- },
1767
+ })),
1683
1768
  handler: async (args) => {
1684
1769
  return await traceRoomSchema(args.repo);
1685
1770
  },
@@ -1689,11 +1774,11 @@ const TOOL_DEFINITIONS = [
1689
1774
  category: "analysis",
1690
1775
  searchHint: "kotlin serialization serializable json schema serialname field type api contract data class",
1691
1776
  description: "Derive JSON field schema from @Serializable data classes. Extracts field names, types, @SerialName remapping, nullable flags, and defaults.",
1692
- schema: {
1777
+ schema: lazySchema(() => ({
1693
1778
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1694
1779
  file_pattern: z.string().optional().describe("Filter by file path substring"),
1695
1780
  class_name: z.string().optional().describe("Filter to a single class by name"),
1696
- },
1781
+ })),
1697
1782
  handler: async (args) => {
1698
1783
  const opts = {};
1699
1784
  if (typeof args.file_pattern === "string")
@@ -1708,10 +1793,10 @@ const TOOL_DEFINITIONS = [
1708
1793
  category: "analysis",
1709
1794
  searchHint: "kotlin flow coroutine operator map filter collect stateIn shareIn catch chain pipeline reactive",
1710
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.",
1711
- schema: {
1796
+ schema: lazySchema(() => ({
1712
1797
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1713
1798
  symbol_name: z.string().describe("Name of the function or property containing the Flow chain"),
1714
- },
1799
+ })),
1715
1800
  handler: async (args) => {
1716
1801
  return await traceFlowChain(args.repo, args.symbol_name);
1717
1802
  },
@@ -1723,11 +1808,11 @@ const TOOL_DEFINITIONS = [
1723
1808
  requiresLanguage: "python",
1724
1809
  searchHint: "python django sqlalchemy orm model relationship foreignkey manytomany entity graph mermaid",
1725
1810
  description: "Extract ORM model relationships (Django ForeignKey/M2M/O2O, SQLAlchemy relationship). JSON or mermaid erDiagram.",
1726
- schema: {
1811
+ schema: lazySchema(() => ({
1727
1812
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1728
1813
  file_pattern: z.string().optional().describe("Filter by file path substring"),
1729
1814
  output_format: z.enum(["json", "mermaid"]).optional().describe("Output as structured JSON or mermaid erDiagram"),
1730
- },
1815
+ })),
1731
1816
  handler: async (args) => {
1732
1817
  const opts = {};
1733
1818
  if (args.file_pattern != null)
@@ -1743,10 +1828,10 @@ const TOOL_DEFINITIONS = [
1743
1828
  requiresLanguage: "python",
1744
1829
  searchHint: "python pytest fixture conftest scope autouse dependency graph session function",
1745
1830
  description: "Extract pytest fixture dependency graph: conftest hierarchy, scope, autouse, fixture-to-fixture deps.",
1746
- schema: {
1831
+ schema: lazySchema(() => ({
1747
1832
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1748
1833
  file_pattern: z.string().optional().describe("Filter by file path substring"),
1749
- },
1834
+ })),
1750
1835
  handler: async (args) => {
1751
1836
  const opts = {};
1752
1837
  if (args.file_pattern != null)
@@ -1760,10 +1845,10 @@ const TOOL_DEFINITIONS = [
1760
1845
  requiresLanguage: "python",
1761
1846
  searchHint: "python django signal receiver celery task middleware management command flask fastapi event wiring",
1762
1847
  description: "Discover implicit control flow: Django signals, Celery tasks/.delay() calls, middleware, management commands, Flask init_app, FastAPI events.",
1763
- schema: {
1848
+ schema: lazySchema(() => ({
1764
1849
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1765
1850
  file_pattern: z.string().optional().describe("Filter by file path substring"),
1766
- },
1851
+ })),
1767
1852
  handler: async (args) => {
1768
1853
  const opts = {};
1769
1854
  if (args.file_pattern != null)
@@ -1777,12 +1862,12 @@ const TOOL_DEFINITIONS = [
1777
1862
  requiresLanguage: "python",
1778
1863
  searchHint: "python ruff lint check bugbear performance simplify security async unused argument",
1779
1864
  description: "Run ruff linter with symbol graph correlation. Configurable rule categories (B, PERF, SIM, UP, S, ASYNC, RET, ARG).",
1780
- schema: {
1865
+ schema: lazySchema(() => ({
1781
1866
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1782
1867
  categories: z.array(z.string()).optional().describe("Rule categories to enable (default: B,PERF,SIM,UP,S,ASYNC,RET,ARG)"),
1783
1868
  file_pattern: z.string().optional().describe("Filter by file path substring"),
1784
1869
  max_results: zFiniteNumber.optional().describe("Max findings to return (default: 100)"),
1785
- },
1870
+ })),
1786
1871
  handler: async (args) => {
1787
1872
  const opts = {};
1788
1873
  if (args.categories != null)
@@ -1800,7 +1885,7 @@ const TOOL_DEFINITIONS = [
1800
1885
  requiresLanguage: "python",
1801
1886
  searchHint: "python pyproject toml dependencies version build system entry points scripts tools ruff pytest mypy",
1802
1887
  description: "Parse pyproject.toml: name, version, Python version, build system, dependencies, optional groups, entry points, configured tools.",
1803
- 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)") })),
1804
1889
  handler: async (args) => { return await parsePyproject(args.repo); },
1805
1890
  },
1806
1891
  {
@@ -1808,13 +1893,13 @@ const TOOL_DEFINITIONS = [
1808
1893
  category: "analysis",
1809
1894
  searchHint: "python typescript nestjs resolve constant value literal alias import default parameter propagation",
1810
1895
  description: "Resolve Python or TypeScript constants and function default values through simple aliases and import chains. Returns literals or explicit unresolved reasons.",
1811
- schema: {
1896
+ schema: lazySchema(() => ({
1812
1897
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1813
1898
  symbol_name: z.string().describe("Constant, function, or method name to resolve"),
1814
1899
  file_pattern: z.string().optional().describe("Filter candidate symbols by file path substring"),
1815
1900
  language: z.enum(["python", "typescript"]).optional().describe("Force resolver language instead of auto-inference"),
1816
1901
  max_depth: zFiniteNumber.optional().describe("Maximum alias/import resolution depth (default: 8)"),
1817
- },
1902
+ })),
1818
1903
  handler: async (args) => {
1819
1904
  const opts = {};
1820
1905
  if (args.file_pattern != null)
@@ -1832,13 +1917,13 @@ const TOOL_DEFINITIONS = [
1832
1917
  requiresLanguage: "python",
1833
1918
  searchHint: "python django view auth csrf login_required middleware mixin route security posture",
1834
1919
  description: "Assess effective Django view security from decorators, mixins, settings middleware, and optional route resolution.",
1835
- schema: {
1920
+ schema: lazySchema(() => ({
1836
1921
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1837
1922
  path: z.string().optional().describe("Django route path to resolve first, e.g. /settings/"),
1838
1923
  symbol_name: z.string().optional().describe("View function/class/method name when you already know the symbol"),
1839
1924
  file_pattern: z.string().optional().describe("Filter candidate symbols by file path substring"),
1840
1925
  settings_file: z.string().optional().describe("Explicit Django settings file path (auto-detects if omitted)"),
1841
- },
1926
+ })),
1842
1927
  handler: async (args) => {
1843
1928
  const opts = {};
1844
1929
  if (args.path != null)
@@ -1858,7 +1943,7 @@ const TOOL_DEFINITIONS = [
1858
1943
  requiresLanguage: "python",
1859
1944
  searchHint: "python django taint data flow source sink request get post redirect mark_safe cursor execute subprocess session trace",
1860
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.",
1861
- schema: {
1946
+ schema: lazySchema(() => ({
1862
1947
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1863
1948
  framework: z.enum(["python-django"]).optional().describe("Currently only python-django is implemented"),
1864
1949
  file_pattern: z.string().optional().describe("Restrict analysis to matching Python files"),
@@ -1866,7 +1951,7 @@ const TOOL_DEFINITIONS = [
1866
1951
  sink_patterns: z.array(z.string()).optional().describe("Optional sink pattern allowlist (defaults to built-in security sinks)"),
1867
1952
  max_depth: zFiniteNumber.optional().describe("Maximum interprocedural helper depth (default: 4)"),
1868
1953
  max_traces: zFiniteNumber.optional().describe("Maximum traces to return before truncation (default: 50)"),
1869
- },
1954
+ })),
1870
1955
  handler: async (args) => {
1871
1956
  const opts = {};
1872
1957
  if (args.framework != null)
@@ -1890,13 +1975,13 @@ const TOOL_DEFINITIONS = [
1890
1975
  requiresLanguage: "python",
1891
1976
  searchHint: "python callers call site usage trace cross module import delay apply_async constructor",
1892
1977
  description: "Find all call sites of a Python symbol: direct calls, method calls, Celery .delay()/.apply_async(), constructor, references.",
1893
- schema: {
1978
+ schema: lazySchema(() => ({
1894
1979
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1895
1980
  target_name: z.string().describe("Name of the target function/class/method"),
1896
1981
  target_file: z.string().optional().describe("Disambiguate target by file path substring"),
1897
1982
  file_pattern: z.string().optional().describe("Restrict caller search scope"),
1898
1983
  max_results: zFiniteNumber.optional().describe("Max callers to return (default: 100)"),
1899
- },
1984
+ })),
1900
1985
  handler: async (args) => {
1901
1986
  const opts = {};
1902
1987
  if (args.target_file != null)
@@ -1914,10 +1999,10 @@ const TOOL_DEFINITIONS = [
1914
1999
  requiresLanguage: "python",
1915
2000
  searchHint: "python django settings security debug secret key allowed hosts csrf middleware cookie hsts cors",
1916
2001
  description: "Audit Django settings.py: 15 security/config checks (DEBUG, SECRET_KEY, CSRF, CORS, HSTS, cookies, sqlite, middleware).",
1917
- schema: {
2002
+ schema: lazySchema(() => ({
1918
2003
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1919
2004
  settings_file: z.string().optional().describe("Explicit settings file path (auto-detects if omitted)"),
1920
- },
2005
+ })),
1921
2006
  handler: async (args) => {
1922
2007
  const opts = {};
1923
2008
  if (args.settings_file != null)
@@ -1931,12 +2016,12 @@ const TOOL_DEFINITIONS = [
1931
2016
  requiresLanguage: "python",
1932
2017
  searchHint: "python mypy type check error strict return incompatible argument missing",
1933
2018
  description: "Run mypy type checker with symbol correlation. Parses error codes, maps to containing symbols.",
1934
- schema: {
2019
+ schema: lazySchema(() => ({
1935
2020
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1936
2021
  file_pattern: z.string().optional().describe("Filter by file path substring"),
1937
2022
  strict: zBool().describe("Enable mypy --strict mode"),
1938
2023
  max_results: zFiniteNumber.optional().describe("Max findings (default: 100)"),
1939
- },
2024
+ })),
1940
2025
  handler: async (args) => {
1941
2026
  const opts = {};
1942
2027
  if (args.file_pattern != null)
@@ -1954,12 +2039,12 @@ const TOOL_DEFINITIONS = [
1954
2039
  requiresLanguage: "python",
1955
2040
  searchHint: "python pyright type check reportMissingImports reportGeneralTypeIssues",
1956
2041
  description: "Run pyright type checker with symbol correlation. Parses JSON diagnostics, maps to containing symbols.",
1957
- schema: {
2042
+ schema: lazySchema(() => ({
1958
2043
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1959
2044
  file_pattern: z.string().optional().describe("Filter by file path substring"),
1960
2045
  strict: zBool().describe("Enable strict level"),
1961
2046
  max_results: zFiniteNumber.optional().describe("Max findings (default: 100)"),
1962
- },
2047
+ })),
1963
2048
  handler: async (args) => {
1964
2049
  const opts = {};
1965
2050
  if (args.file_pattern != null)
@@ -1977,11 +2062,11 @@ const TOOL_DEFINITIONS = [
1977
2062
  requiresLanguage: "python",
1978
2063
  searchHint: "python dependency version outdated vulnerable CVE pypi osv requirements pyproject",
1979
2064
  description: "Python dependency analysis: parse pyproject.toml/requirements.txt, detect unpinned deps, optional PyPI freshness, optional OSV.dev CVE scan.",
1980
- schema: {
2065
+ schema: lazySchema(() => ({
1981
2066
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1982
2067
  check_pypi: zBool().describe("Check PyPI for latest versions (network, opt-in)"),
1983
2068
  check_vulns: zBool().describe("Check OSV.dev for CVEs (network, opt-in)"),
1984
- },
2069
+ })),
1985
2070
  handler: async (args) => {
1986
2071
  const opts = {};
1987
2072
  if (args.check_pypi != null)
@@ -1997,12 +2082,12 @@ const TOOL_DEFINITIONS = [
1997
2082
  requiresLanguage: "python",
1998
2083
  searchHint: "python fastapi depends dependency injection security scopes oauth2 authentication auth endpoint",
1999
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.",
2000
- schema: {
2085
+ schema: lazySchema(() => ({
2001
2086
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2002
2087
  file_pattern: z.string().optional().describe("Filter by file path substring"),
2003
2088
  endpoint: z.string().optional().describe("Focus on a specific endpoint function name"),
2004
2089
  max_depth: zFiniteNumber.optional().describe("Max dependency tree depth (default: 5)"),
2005
- },
2090
+ })),
2006
2091
  handler: async (args) => {
2007
2092
  const opts = {};
2008
2093
  if (args.file_pattern != null)
@@ -2020,12 +2105,12 @@ const TOOL_DEFINITIONS = [
2020
2105
  requiresLanguage: "python",
2021
2106
  searchHint: "python async await asyncio blocking sync requests sleep subprocess django sqlalchemy ORM coroutine fastapi",
2022
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.",
2023
- schema: {
2108
+ schema: lazySchema(() => ({
2024
2109
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2025
2110
  file_pattern: z.string().optional().describe("Filter by file path substring"),
2026
2111
  rules: z.array(z.string()).optional().describe("Subset of rules to run"),
2027
2112
  max_results: zFiniteNumber.optional().describe("Max findings (default: 200)"),
2028
- },
2113
+ })),
2029
2114
  handler: async (args) => {
2030
2115
  const opts = {};
2031
2116
  if (args.file_pattern != null)
@@ -2043,11 +2128,11 @@ const TOOL_DEFINITIONS = [
2043
2128
  requiresLanguage: "python",
2044
2129
  searchHint: "python pydantic basemodel fastapi schema request response contract validator field constraint type classdiagram",
2045
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.",
2046
- schema: {
2131
+ schema: lazySchema(() => ({
2047
2132
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2048
2133
  file_pattern: z.string().optional().describe("Filter by file path substring"),
2049
2134
  output_format: z.enum(["json", "mermaid"]).optional().describe("Output as structured JSON or mermaid classDiagram"),
2050
- },
2135
+ })),
2051
2136
  handler: async (args) => {
2052
2137
  const opts = {};
2053
2138
  if (args.file_pattern != null)
@@ -2063,11 +2148,11 @@ const TOOL_DEFINITIONS = [
2063
2148
  requiresLanguage: "python",
2064
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",
2065
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.",
2066
- schema: {
2151
+ schema: lazySchema(() => ({
2067
2152
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2068
2153
  file_pattern: z.string().optional().describe("Filter by file path substring"),
2069
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"),
2070
- },
2155
+ })),
2071
2156
  handler: async (args) => {
2072
2157
  const opts = {};
2073
2158
  if (args.file_pattern != null)
@@ -2084,10 +2169,10 @@ const TOOL_DEFINITIONS = [
2084
2169
  requiresLanguage: "php",
2085
2170
  searchHint: "php namespace resolve PSR-4 autoload composer class file path yii2 laravel symfony",
2086
2171
  description: "Resolve a PHP FQCN to file path via composer.json PSR-4 autoload mapping.",
2087
- schema: {
2172
+ schema: lazySchema(() => ({
2088
2173
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2089
2174
  class_name: z.string().describe("Fully-qualified class name, e.g. 'App\\\\Models\\\\User'"),
2090
- },
2175
+ })),
2091
2176
  handler: async (args) => {
2092
2177
  return await resolvePhpNamespace(args.repo, args.class_name);
2093
2178
  },
@@ -2098,10 +2183,10 @@ const TOOL_DEFINITIONS = [
2098
2183
  requiresLanguage: "php",
2099
2184
  searchHint: "php event listener trigger handler chain yii2 laravel observer dispatch",
2100
2185
  description: "Trace PHP event → listener chains: find trigger() calls and matching on() handlers.",
2101
- schema: {
2186
+ schema: lazySchema(() => ({
2102
2187
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2103
2188
  event_name: z.string().optional().describe("Filter by specific event name"),
2104
- },
2189
+ })),
2105
2190
  handler: async (args) => {
2106
2191
  const opts = {};
2107
2192
  if (typeof args.event_name === "string")
@@ -2115,10 +2200,10 @@ const TOOL_DEFINITIONS = [
2115
2200
  requiresLanguage: "php",
2116
2201
  searchHint: "php view render template controller widget yii2 laravel blade",
2117
2202
  description: "Map PHP controller render() calls to view files. Yii2/Laravel convention-aware.",
2118
- schema: {
2203
+ schema: lazySchema(() => ({
2119
2204
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2120
2205
  controller: z.string().optional().describe("Filter by controller class name"),
2121
- },
2206
+ })),
2122
2207
  handler: async (args) => {
2123
2208
  const opts = {};
2124
2209
  if (typeof args.controller === "string")
@@ -2132,10 +2217,10 @@ const TOOL_DEFINITIONS = [
2132
2217
  requiresLanguage: "php",
2133
2218
  searchHint: "php service locator DI container component resolve yii2 laravel facade provider",
2134
2219
  description: "Resolve PHP service locator references (Yii::$app->X, Laravel facades) to concrete classes via config parsing.",
2135
- schema: {
2220
+ schema: lazySchema(() => ({
2136
2221
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2137
2222
  service_name: z.string().optional().describe("Filter by specific service name (e.g. 'db', 'user', 'cache')"),
2138
- },
2223
+ })),
2139
2224
  handler: async (args) => {
2140
2225
  const opts = {};
2141
2226
  if (typeof args.service_name === "string")
@@ -2149,11 +2234,11 @@ const TOOL_DEFINITIONS = [
2149
2234
  requiresLanguage: "php",
2150
2235
  searchHint: "php security scan audit vulnerability injection XSS CSRF SQL eval exec unserialize",
2151
2236
  description: "Scan PHP code for security vulnerabilities: SQL injection, XSS, eval, exec, unserialize, file inclusion. Parallel pattern checks.",
2152
- schema: {
2237
+ schema: lazySchema(() => ({
2153
2238
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2154
2239
  file_pattern: z.string().optional().describe("Glob pattern to filter scanned files (default: '*.php')"),
2155
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"),
2156
- },
2241
+ })),
2157
2242
  handler: async (args) => {
2158
2243
  const opts = {};
2159
2244
  if (typeof args.file_pattern === "string")
@@ -2169,11 +2254,11 @@ const TOOL_DEFINITIONS = [
2169
2254
  requiresLanguage: "php",
2170
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",
2171
2256
  description: "Compound PHP project audit: security scan + ActiveRecord analysis + N+1 detection + god model detection + health score. Runs checks in parallel.",
2172
- schema: {
2257
+ schema: lazySchema(() => ({
2173
2258
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2174
2259
  file_pattern: z.string().optional().describe("Glob pattern to filter analyzed files"),
2175
2260
  checks: z.string().optional().describe("Comma-separated checks: n_plus_one, god_model, activerecord, security, events, views, services, namespace. Default: all"),
2176
- },
2261
+ })),
2177
2262
  handler: async (args) => {
2178
2263
  const opts = {};
2179
2264
  if (typeof args.file_pattern === "string")
@@ -2190,11 +2275,11 @@ const TOOL_DEFINITIONS = [
2190
2275
  category: "conversations",
2191
2276
  searchHint: "consolidate memories dream knowledge MEMORY.md decisions solutions patterns",
2192
2277
  description: "Consolidate conversations into MEMORY.md — decisions, solutions, patterns.",
2193
- schema: {
2278
+ schema: lazySchema(() => ({
2194
2279
  project_path: z.string().optional().describe("Project path (auto-detects from cwd if omitted)"),
2195
2280
  output_path: z.string().optional().describe("Custom output file path (default: MEMORY.md in project root)"),
2196
2281
  min_confidence: z.enum(["high", "medium", "low"]).optional().describe("Minimum confidence level for extracted memories (default: low)"),
2197
- },
2282
+ })),
2198
2283
  handler: async (args) => {
2199
2284
  const opts = {};
2200
2285
  if (typeof args.output_path === "string")
@@ -2210,9 +2295,9 @@ const TOOL_DEFINITIONS = [
2210
2295
  category: "conversations",
2211
2296
  searchHint: "read memory MEMORY.md institutional knowledge past decisions",
2212
2297
  description: "Read MEMORY.md knowledge file with past decisions and patterns.",
2213
- schema: {
2298
+ schema: lazySchema(() => ({
2214
2299
  project_path: z.string().optional().describe("Project path (default: current directory)"),
2215
- },
2300
+ })),
2216
2301
  handler: async (args) => {
2217
2302
  const result = await readMemory(args.project_path);
2218
2303
  if (!result)
@@ -2226,7 +2311,7 @@ const TOOL_DEFINITIONS = [
2226
2311
  category: "meta",
2227
2312
  searchHint: "create plan multi-step analysis workflow coordinator scratchpad",
2228
2313
  description: "Create multi-step analysis plan with shared scratchpad and dependencies.",
2229
- schema: {
2314
+ schema: lazySchema(() => ({
2230
2315
  title: z.string().describe("Plan title describing the analysis goal"),
2231
2316
  steps: z.union([
2232
2317
  z.array(z.object({
@@ -2238,7 +2323,7 @@ const TOOL_DEFINITIONS = [
2238
2323
  })),
2239
2324
  z.string().transform((s) => JSON.parse(s)),
2240
2325
  ]).describe("Steps array: {description, tool, args, result_key?, depends_on?}. JSON string OK."),
2241
- },
2326
+ })),
2242
2327
  handler: async (args) => {
2243
2328
  const result = await createAnalysisPlan(args.title, args.steps);
2244
2329
  return result;
@@ -2249,11 +2334,11 @@ const TOOL_DEFINITIONS = [
2249
2334
  category: "meta",
2250
2335
  searchHint: "scratchpad write store knowledge cross-step data persist",
2251
2336
  description: "Write key-value to plan scratchpad for cross-step knowledge sharing.",
2252
- schema: {
2337
+ schema: lazySchema(() => ({
2253
2338
  plan_id: z.string().describe("Analysis plan identifier"),
2254
2339
  key: z.string().describe("Key name for the entry"),
2255
2340
  value: z.string().describe("Value to store"),
2256
- },
2341
+ })),
2257
2342
  handler: async (args) => writeScratchpad(args.plan_id, args.key, args.value),
2258
2343
  },
2259
2344
  {
@@ -2261,10 +2346,10 @@ const TOOL_DEFINITIONS = [
2261
2346
  category: "meta",
2262
2347
  searchHint: "scratchpad read retrieve knowledge entry",
2263
2348
  description: "Read a key from a plan's scratchpad. Returns the stored value or null if not found.",
2264
- schema: {
2349
+ schema: lazySchema(() => ({
2265
2350
  plan_id: z.string().describe("Analysis plan identifier"),
2266
2351
  key: z.string().describe("Key name to read"),
2267
- },
2352
+ })),
2268
2353
  handler: async (args) => {
2269
2354
  const result = await readScratchpad(args.plan_id, args.key);
2270
2355
  return result ?? { error: "Key not found in scratchpad" };
@@ -2275,9 +2360,9 @@ const TOOL_DEFINITIONS = [
2275
2360
  category: "meta",
2276
2361
  searchHint: "scratchpad list entries keys",
2277
2362
  description: "List all entries in a plan's scratchpad with their sizes.",
2278
- schema: {
2363
+ schema: lazySchema(() => ({
2279
2364
  plan_id: z.string().describe("Analysis plan identifier"),
2280
- },
2365
+ })),
2281
2366
  handler: (args) => listScratchpad(args.plan_id),
2282
2367
  },
2283
2368
  {
@@ -2285,12 +2370,12 @@ const TOOL_DEFINITIONS = [
2285
2370
  category: "meta",
2286
2371
  searchHint: "update step status plan progress completed failed",
2287
2372
  description: "Update step status in plan. Auto-updates plan status on completion.",
2288
- schema: {
2373
+ schema: lazySchema(() => ({
2289
2374
  plan_id: z.string().describe("Analysis plan identifier"),
2290
2375
  step_id: z.string().describe("Step identifier (e.g. step_1)"),
2291
2376
  status: z.enum(["pending", "in_progress", "completed", "failed", "skipped"]).describe("New status for the step"),
2292
2377
  error: z.string().optional().describe("Error message if status is 'failed'"),
2293
- },
2378
+ })),
2294
2379
  handler: async (args) => {
2295
2380
  const result = await updateStepStatus(args.plan_id, args.step_id, args.status, args.error);
2296
2381
  return result;
@@ -2301,9 +2386,9 @@ const TOOL_DEFINITIONS = [
2301
2386
  category: "meta",
2302
2387
  searchHint: "get plan status steps progress",
2303
2388
  description: "Get the current state of an analysis plan including all step statuses.",
2304
- schema: {
2389
+ schema: lazySchema(() => ({
2305
2390
  plan_id: z.string().describe("Analysis plan identifier"),
2306
- },
2391
+ })),
2307
2392
  handler: async (args) => {
2308
2393
  const plan = getPlan(args.plan_id);
2309
2394
  return plan ?? { error: "Plan not found" };
@@ -2314,7 +2399,7 @@ const TOOL_DEFINITIONS = [
2314
2399
  category: "meta",
2315
2400
  searchHint: "list plans active analysis workflows",
2316
2401
  description: "List all active analysis plans with their completion status.",
2317
- schema: {},
2402
+ schema: lazySchema(() => ({})),
2318
2403
  handler: async () => listPlans(),
2319
2404
  },
2320
2405
  // --- Review diff ---
@@ -2323,7 +2408,7 @@ const TOOL_DEFINITIONS = [
2323
2408
  category: "diff",
2324
2409
  searchHint: "review diff static analysis git changes secrets breaking-changes complexity dead-code blast-radius",
2325
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.",
2326
- schema: {
2411
+ schema: lazySchema(() => ({
2327
2412
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2328
2413
  since: z.string().optional().describe("Base git ref (default: HEAD~1)"),
2329
2414
  until: z.string().optional().describe("Target ref. Default: HEAD. Special: WORKING, STAGED"),
@@ -2332,7 +2417,7 @@ const TOOL_DEFINITIONS = [
2332
2417
  token_budget: zNum().describe("Max tokens (default: 15000)"),
2333
2418
  max_files: zNum().describe("Warn above N files (default: 50)"),
2334
2419
  check_timeout_ms: zNum().describe("Per-check timeout ms (default: 8000)"),
2335
- },
2420
+ })),
2336
2421
  handler: async (args) => {
2337
2422
  const checksArr = args.checks
2338
2423
  ? args.checks.split(",").map((c) => c.trim()).filter(Boolean)
@@ -2368,7 +2453,7 @@ const TOOL_DEFINITIONS = [
2368
2453
  searchHint: "usage statistics tool calls tokens timing metrics",
2369
2454
  outputSchema: OutputSchemas.usageStats,
2370
2455
  description: "Show usage statistics for all CodeSift tool calls (call counts, tokens, timing, repos)",
2371
- schema: {},
2456
+ schema: lazySchema(() => ({})),
2372
2457
  handler: async () => {
2373
2458
  const stats = await getUsageStats();
2374
2459
  const { createRequire } = await import("node:module");
@@ -2383,9 +2468,9 @@ const TOOL_DEFINITIONS = [
2383
2468
  category: "session",
2384
2469
  searchHint: "session context snapshot compaction summary explored symbols files queries",
2385
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.",
2386
- schema: {
2471
+ schema: lazySchema(() => ({
2387
2472
  repo: z.string().optional().describe("Filter to specific repo. Default: most recent repo."),
2388
- },
2473
+ })),
2389
2474
  handler: async (args) => {
2390
2475
  return formatSnapshot(getSessionState(), args.repo);
2391
2476
  },
@@ -2395,10 +2480,10 @@ const TOOL_DEFINITIONS = [
2395
2480
  category: "session",
2396
2481
  searchHint: "session context full explored symbols files queries negative evidence",
2397
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.",
2398
- schema: {
2483
+ schema: lazySchema(() => ({
2399
2484
  repo: z.string().optional().describe("Filter to specific repo"),
2400
2485
  include_stale: zBool().describe("Include stale negative evidence entries (default: false)"),
2401
- },
2486
+ })),
2402
2487
  handler: async (args) => {
2403
2488
  const includeStale = args.include_stale === true || args.include_stale === "true";
2404
2489
  return getContext(args.repo, includeStale);
@@ -2410,10 +2495,10 @@ const TOOL_DEFINITIONS = [
2410
2495
  category: "analysis",
2411
2496
  searchHint: "project profile stack conventions middleware routes rate-limits auth detection",
2412
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.",
2413
- schema: {
2498
+ schema: lazySchema(() => ({
2414
2499
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2415
2500
  force: zBool().describe("Ignore cached results and re-analyze"),
2416
- },
2501
+ })),
2417
2502
  handler: async (args) => {
2418
2503
  const result = await analyzeProject(args.repo, {
2419
2504
  force: args.force,
@@ -2426,7 +2511,7 @@ const TOOL_DEFINITIONS = [
2426
2511
  category: "meta",
2427
2512
  searchHint: "extractor version cache invalidation profile parser languages",
2428
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.",
2429
- schema: {},
2514
+ schema: lazySchema(() => ({})),
2430
2515
  handler: async () => getExtractorVersions(),
2431
2516
  },
2432
2517
  // --- Composite tools ---
@@ -2435,12 +2520,12 @@ const TOOL_DEFINITIONS = [
2435
2520
  category: "analysis",
2436
2521
  searchHint: "audit scan code quality CQ gates dead code clones complexity patterns",
2437
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).",
2438
- schema: {
2523
+ schema: lazySchema(() => ({
2439
2524
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2440
2525
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
2441
2526
  include_tests: zBool().describe("Include test files (default: false)"),
2442
2527
  checks: z.string().optional().describe("Comma-separated CQ gates to check (default: all). E.g. 'CQ8,CQ11,CQ14'"),
2443
- },
2528
+ })),
2444
2529
  handler: async (args) => {
2445
2530
  const checks = args.checks ? args.checks.split(",").map(s => s.trim()) : undefined;
2446
2531
  const opts = {};
@@ -2460,9 +2545,9 @@ const TOOL_DEFINITIONS = [
2460
2545
  category: "meta",
2461
2546
  searchHint: "index status indexed repo check files symbols languages",
2462
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.",
2463
- schema: {
2548
+ schema: lazySchema(() => ({
2464
2549
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2465
- },
2550
+ })),
2466
2551
  handler: async (args) => {
2467
2552
  const result = await indexStatus(args.repo);
2468
2553
  if (!result.indexed)
@@ -2487,13 +2572,13 @@ const TOOL_DEFINITIONS = [
2487
2572
  category: "analysis",
2488
2573
  searchHint: "performance perf hotspot N+1 unbounded query sync handler pagination findMany pLimit",
2489
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.",
2490
- schema: {
2575
+ schema: lazySchema(() => ({
2491
2576
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2492
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"),
2493
2578
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
2494
2579
  include_tests: zBool().describe("Include test files (default: false)"),
2495
2580
  max_results: zNum().describe("Max findings to return (default: 50)"),
2496
- },
2581
+ })),
2497
2582
  handler: async (args) => {
2498
2583
  const patterns = args.patterns
2499
2584
  ? args.patterns.split(",").map((s) => s.trim()).filter(Boolean)
@@ -2516,13 +2601,13 @@ const TOOL_DEFINITIONS = [
2516
2601
  category: "architecture",
2517
2602
  searchHint: "fan-in fan-out coupling dependencies imports hub afferent efferent instability threshold",
2518
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.",
2519
- schema: {
2604
+ schema: lazySchema(() => ({
2520
2605
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2521
2606
  path: z.string().optional().describe("Focus on files in this directory"),
2522
2607
  top_n: zNum().describe("How many entries per list (default: 20)"),
2523
2608
  min_fan_in: zNum().describe("Only return files with fan_in >= this value (default: 0). Use for audits."),
2524
2609
  min_fan_out: zNum().describe("Only return files with fan_out >= this value (default: 0). Use for audits."),
2525
- },
2610
+ })),
2526
2611
  handler: async (args) => {
2527
2612
  const opts = {};
2528
2613
  if (args.path != null)
@@ -2542,14 +2627,14 @@ const TOOL_DEFINITIONS = [
2542
2627
  category: "architecture",
2543
2628
  searchHint: "co-change temporal coupling git history Jaccard co-commit correlation cluster",
2544
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.",
2545
- schema: {
2630
+ schema: lazySchema(() => ({
2546
2631
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2547
2632
  since_days: zNum().describe("Analyze last N days of history (default: 180)"),
2548
2633
  min_support: zNum().describe("Minimum co-commits to include a pair (default: 3)"),
2549
2634
  min_jaccard: zNum().describe("Minimum Jaccard similarity threshold (default: 0.3)"),
2550
2635
  path: z.string().optional().describe("Focus on files in this directory"),
2551
2636
  top_n: zNum().describe("Max pairs to return (default: 30)"),
2552
- },
2637
+ })),
2553
2638
  handler: async (args) => {
2554
2639
  const opts = {};
2555
2640
  if (args.since_days != null)
@@ -2571,12 +2656,12 @@ const TOOL_DEFINITIONS = [
2571
2656
  category: "architecture",
2572
2657
  searchHint: "architecture summary overview structure stack framework communities coupling circular dependencies entry points",
2573
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.",
2574
- schema: {
2659
+ schema: lazySchema(() => ({
2575
2660
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2576
2661
  focus: z.string().optional().describe("Focus on this directory path"),
2577
2662
  output_format: z.enum(["text", "mermaid"]).optional().describe("Output format (default: text)"),
2578
2663
  token_budget: zNum().describe("Max tokens for output"),
2579
- },
2664
+ })),
2580
2665
  handler: async (args) => {
2581
2666
  const opts = {};
2582
2667
  if (args.focus != null)
@@ -2594,10 +2679,10 @@ const TOOL_DEFINITIONS = [
2594
2679
  category: "analysis",
2595
2680
  searchHint: "explain query SQL Prisma ORM database performance EXPLAIN ANALYZE findMany pagination index",
2596
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.",
2597
- schema: {
2682
+ schema: lazySchema(() => ({
2598
2683
  code: z.string().describe("Prisma code snippet (e.g. prisma.user.findMany({...}))"),
2599
2684
  dialect: z.enum(["postgresql", "mysql", "sqlite"]).optional().describe("SQL dialect (default: postgresql)"),
2600
- },
2685
+ })),
2601
2686
  handler: async (args) => {
2602
2687
  const eqOpts = {};
2603
2688
  if (args.dialect != null)
@@ -2629,10 +2714,10 @@ const TOOL_DEFINITIONS = [
2629
2714
  category: "nestjs",
2630
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",
2631
2716
  description: "One-call NestJS architecture audit: modules, DI, guards, routes, lifecycle, patterns, GraphQL, WebSocket, schedule, TypeORM, microservices.",
2632
- schema: {
2717
+ schema: lazySchema(() => ({
2633
2718
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2634
2719
  checks: z.string().optional().describe("Comma-separated checks (default: all). Options: modules,routes,di,guards,lifecycle,patterns,graphql,websocket,schedule,typeorm,microservice"),
2635
- },
2720
+ })),
2636
2721
  handler: async (args) => {
2637
2722
  const checks = args.checks?.split(",").map((s) => s.trim()).filter(Boolean);
2638
2723
  return nestAudit(args.repo ?? "", checks ? { checks } : undefined);
@@ -2644,11 +2729,11 @@ const TOOL_DEFINITIONS = [
2644
2729
  category: "meta",
2645
2730
  searchHint: "audit agent config CLAUDE.md cursorrules stale symbols dead paths token waste redundancy",
2646
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.",
2647
- schema: {
2732
+ schema: lazySchema(() => ({
2648
2733
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2649
2734
  config_path: z.string().optional().describe("Path to config file (default: CLAUDE.md in repo root)"),
2650
2735
  compare_with: z.string().optional().describe("Path to second config file for redundancy detection"),
2651
- },
2736
+ })),
2652
2737
  handler: async (args) => {
2653
2738
  const opts = {};
2654
2739
  if (args.config_path != null)
@@ -2689,11 +2774,11 @@ const TOOL_DEFINITIONS = [
2689
2774
  category: "analysis",
2690
2775
  searchHint: "test impact analysis affected tests changed files CI confidence which tests to run",
2691
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.",
2692
- schema: {
2777
+ schema: lazySchema(() => ({
2693
2778
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2694
2779
  since: z.string().optional().describe("Git ref to compare from (default: HEAD~1)"),
2695
2780
  until: z.string().optional().describe("Git ref to compare to (default: HEAD)"),
2696
- },
2781
+ })),
2697
2782
  handler: async (args) => {
2698
2783
  const opts = {};
2699
2784
  if (args.since != null)
@@ -2722,12 +2807,12 @@ const TOOL_DEFINITIONS = [
2722
2807
  category: "analysis",
2723
2808
  searchHint: "dependency audit npm vulnerabilities CVE licenses outdated freshness lockfile drift supply chain",
2724
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.",
2725
- schema: {
2810
+ schema: lazySchema(() => ({
2726
2811
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2727
2812
  workspace_path: z.string().optional().describe("Workspace path (default: index root)"),
2728
2813
  skip_licenses: zBool().describe("Skip license check (faster, default: false)"),
2729
2814
  min_severity: z.enum(["low", "moderate", "high", "critical"]).optional().describe("Filter vulnerabilities by minimum severity"),
2730
- },
2815
+ })),
2731
2816
  handler: async (args) => {
2732
2817
  const opts = {};
2733
2818
  if (args.workspace_path != null)
@@ -2773,12 +2858,12 @@ const TOOL_DEFINITIONS = [
2773
2858
  category: "analysis",
2774
2859
  searchHint: "migration lint squawk SQL postgresql safety linter unsafe-migration not-null drop-column alter-column-type concurrently",
2775
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/.",
2776
- schema: {
2861
+ schema: lazySchema(() => ({
2777
2862
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2778
2863
  migration_glob: z.string().optional().describe("Custom migration file glob pattern"),
2779
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)"),
2780
2865
  pg_version: z.string().optional().describe("PostgreSQL version for version-aware rules (e.g. '13')"),
2781
- },
2866
+ })),
2782
2867
  handler: async (args) => {
2783
2868
  const opts = {};
2784
2869
  if (args.migration_glob != null)
@@ -2813,10 +2898,10 @@ const TOOL_DEFINITIONS = [
2813
2898
  category: "analysis",
2814
2899
  searchHint: "prisma schema analyze ast model field index foreign-key relation soft-delete enum coverage",
2815
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).",
2816
- schema: {
2901
+ schema: lazySchema(() => ({
2817
2902
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2818
2903
  schema_path: z.string().optional().describe("Path to schema.prisma (default: auto-detected)"),
2819
- },
2904
+ })),
2820
2905
  handler: async (args) => {
2821
2906
  const opts = {};
2822
2907
  if (args.schema_path != null)
@@ -2858,11 +2943,11 @@ const TOOL_DEFINITIONS = [
2858
2943
  category: "analysis",
2859
2944
  searchHint: "astro islands client hydration directives framework",
2860
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.",
2861
- schema: {
2946
+ schema: lazySchema(() => ({
2862
2947
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2863
2948
  path_prefix: z.string().optional().describe("Only scan files under this path prefix"),
2864
2949
  include_recommendations: z.boolean().default(true).describe("Include optimization recommendations (default: true)"),
2865
- },
2950
+ })),
2866
2951
  handler: async (args) => {
2867
2952
  const opts = {};
2868
2953
  if (args.repo != null)
@@ -2879,12 +2964,12 @@ const TOOL_DEFINITIONS = [
2879
2964
  category: "analysis",
2880
2965
  searchHint: "astro hydration audit anti-patterns client load",
2881
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.",
2882
- schema: {
2967
+ schema: lazySchema(() => ({
2883
2968
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2884
2969
  severity: z.enum(["all", "warnings", "errors"]).default("all").describe("Filter issues by severity (default: all)"),
2885
2970
  path_prefix: z.string().optional().describe("Only scan files under this path prefix"),
2886
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"),
2887
- },
2972
+ })),
2888
2973
  handler: async (args) => {
2889
2974
  const opts = {};
2890
2975
  if (args.repo != null)
@@ -2903,11 +2988,11 @@ const TOOL_DEFINITIONS = [
2903
2988
  category: "navigation",
2904
2989
  searchHint: "astro routes pages endpoints file-based routing",
2905
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.",
2906
- schema: {
2991
+ schema: lazySchema(() => ({
2907
2992
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2908
2993
  include_endpoints: z.boolean().default(true).describe("Include API endpoint routes (default: true)"),
2909
2994
  output_format: z.enum(["json", "tree", "table"]).default("json").describe("Output format: json | tree | table (default: json)"),
2910
- },
2995
+ })),
2911
2996
  handler: async (args) => {
2912
2997
  const opts = {};
2913
2998
  if (args.repo != null)
@@ -2924,9 +3009,9 @@ const TOOL_DEFINITIONS = [
2924
3009
  category: "analysis",
2925
3010
  searchHint: "astro config integrations adapter output mode",
2926
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.",
2927
- schema: {
3012
+ schema: lazySchema(() => ({
2928
3013
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2929
- },
3014
+ })),
2930
3015
  handler: async (args) => {
2931
3016
  const index = await getCodeIndex(args.repo ?? "");
2932
3017
  if (!index)
@@ -2939,10 +3024,10 @@ const TOOL_DEFINITIONS = [
2939
3024
  category: "analysis",
2940
3025
  searchHint: "astro actions defineAction zod refine passthrough multipart file enctype audit",
2941
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.",
2942
- schema: {
3027
+ schema: lazySchema(() => ({
2943
3028
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2944
3029
  severity: z.enum(["all", "warnings", "errors"]).default("all").describe("Filter issues by severity (default: all)"),
2945
- },
3030
+ })),
2946
3031
  handler: async (args) => {
2947
3032
  const opts = {};
2948
3033
  if (args.repo != null)
@@ -2957,10 +3042,10 @@ const TOOL_DEFINITIONS = [
2957
3042
  category: "analysis",
2958
3043
  searchHint: "astro content collections defineCollection zod schema reference glob loader frontmatter",
2959
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.",
2960
- schema: {
3045
+ schema: lazySchema(() => ({
2961
3046
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2962
3047
  validate_entries: z.boolean().default(true).describe("Validate entry frontmatter against required schema fields (default: true)"),
2963
- },
3048
+ })),
2964
3049
  handler: async (args) => {
2965
3050
  const index = await getCodeIndex(args.repo ?? "");
2966
3051
  if (!index)
@@ -2976,10 +3061,10 @@ const TOOL_DEFINITIONS = [
2976
3061
  category: "analysis",
2977
3062
  searchHint: "astro meta audit full health check score gates recommendations islands hydration routes config actions content migration patterns",
2978
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.",
2979
- schema: {
3064
+ schema: lazySchema(() => ({
2980
3065
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2981
3066
  skip: z.array(z.string()).optional().describe("Sections to skip: config, hydration, routes, actions, content, migration, patterns"),
2982
- },
3067
+ })),
2983
3068
  handler: async (args) => {
2984
3069
  const opts = {};
2985
3070
  if (args.repo != null)
@@ -2995,13 +3080,13 @@ const TOOL_DEFINITIONS = [
2995
3080
  category: "graph",
2996
3081
  searchHint: "hono middleware chain trace order scope auth use conditional applied_when if method header path basicAuth gated",
2997
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.",
2998
- schema: {
3083
+ schema: lazySchema(() => ({
2999
3084
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3000
3085
  path: z.string().optional().describe("Route path to look up (e.g. '/api/users/:id'). Omit for scope or app-wide query."),
3001
3086
  method: z.string().optional().describe("HTTP method filter (GET, POST, etc.). Only used in route mode."),
3002
3087
  scope: z.string().optional().describe("Exact middleware scope literal (e.g. '/posts/*'). Mutually exclusive with path."),
3003
3088
  only_conditional: z.boolean().optional().describe("Filter entries to those whose applied_when field is populated (conditional middleware)."),
3004
- },
3089
+ })),
3005
3090
  handler: async (args) => {
3006
3091
  const { traceMiddlewareChain } = await import("./tools/hono-middleware-chain.js");
3007
3092
  const opts = {};
@@ -3017,11 +3102,11 @@ const TOOL_DEFINITIONS = [
3017
3102
  category: "analysis",
3018
3103
  searchHint: "hono overview analyze app routes middleware runtime env bindings rpc",
3019
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.",
3020
- schema: {
3105
+ schema: lazySchema(() => ({
3021
3106
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3022
3107
  entry_file: z.string().optional().describe("Hono entry file (auto-detected if omitted)"),
3023
3108
  force_refresh: z.boolean().optional().describe("Clear cache and rebuild"),
3024
- },
3109
+ })),
3025
3110
  handler: async (args) => {
3026
3111
  const { analyzeHonoApp } = await import("./tools/hono-analyze-app.js");
3027
3112
  return await analyzeHonoApp(args.repo, args.entry_file, args.force_refresh);
@@ -3032,10 +3117,10 @@ const TOOL_DEFINITIONS = [
3032
3117
  category: "analysis",
3033
3118
  searchHint: "hono context flow c.set c.get c.var c.env middleware variable unguarded",
3034
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.",
3035
- schema: {
3120
+ schema: lazySchema(() => ({
3036
3121
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3037
3122
  variable: z.string().optional().describe("Specific variable name to trace (default: all)"),
3038
- },
3123
+ })),
3039
3124
  handler: async (args) => {
3040
3125
  const { traceContextFlow } = await import("./tools/hono-context-flow.js");
3041
3126
  return await traceContextFlow(args.repo, args.variable);
@@ -3046,11 +3131,11 @@ const TOOL_DEFINITIONS = [
3046
3131
  category: "analysis",
3047
3132
  searchHint: "hono openapi contract api schema createRoute zValidator",
3048
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).",
3049
- schema: {
3134
+ schema: lazySchema(() => ({
3050
3135
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3051
3136
  entry_file: z.string().optional().describe("Hono entry file (auto-detected if omitted)"),
3052
3137
  format: z.enum(["openapi", "summary"]).optional().describe("Output format (default: openapi)"),
3053
- },
3138
+ })),
3054
3139
  handler: async (args) => {
3055
3140
  const { extractApiContract } = await import("./tools/hono-api-contract.js");
3056
3141
  return await extractApiContract(args.repo, args.entry_file, args.format);
@@ -3061,9 +3146,9 @@ const TOOL_DEFINITIONS = [
3061
3146
  category: "analysis",
3062
3147
  searchHint: "hono rpc client type export typeof slow pattern Issue 3869 compile time",
3063
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.",
3064
- schema: {
3149
+ schema: lazySchema(() => ({
3065
3150
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3066
- },
3151
+ })),
3067
3152
  handler: async (args) => {
3068
3153
  const { traceRpcTypes } = await import("./tools/hono-rpc-types.js");
3069
3154
  return await traceRpcTypes(args.repo);
@@ -3074,9 +3159,9 @@ const TOOL_DEFINITIONS = [
3074
3159
  category: "security",
3075
3160
  searchHint: "hono security audit rate limit secure headers auth order csrf env regression createMiddleware BlankEnv Issue 3587",
3076
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.",
3077
- schema: {
3162
+ schema: lazySchema(() => ({
3078
3163
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3079
- },
3164
+ })),
3080
3165
  handler: async (args) => {
3081
3166
  const { auditHonoSecurity } = await import("./tools/hono-security.js");
3082
3167
  return await auditHonoSecurity(args.repo);
@@ -3087,10 +3172,10 @@ const TOOL_DEFINITIONS = [
3087
3172
  category: "reporting",
3088
3173
  searchHint: "hono routes visualize mermaid tree diagram documentation",
3089
3174
  description: "Produce a visualization of Hono routing topology. Supports 'mermaid' (diagram) and 'tree' (ASCII) formats.",
3090
- schema: {
3175
+ schema: lazySchema(() => ({
3091
3176
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3092
3177
  format: z.enum(["mermaid", "tree"]).optional().describe("Output format (default: tree)"),
3093
- },
3178
+ })),
3094
3179
  handler: async (args) => {
3095
3180
  const { visualizeHonoRoutes } = await import("./tools/hono-visualize.js");
3096
3181
  return await visualizeHonoRoutes(args.repo, args.format);
@@ -3102,11 +3187,11 @@ const TOOL_DEFINITIONS = [
3102
3187
  category: "analysis",
3103
3188
  searchHint: "hono inline handler analyze c.json c.text status response error db fetch context",
3104
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.",
3105
- schema: {
3190
+ schema: lazySchema(() => ({
3106
3191
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3107
3192
  method: z.string().optional().describe("HTTP method filter (case-insensitive)"),
3108
3193
  path: z.string().optional().describe("Route path filter (exact match, e.g. '/users/:id')"),
3109
- },
3194
+ })),
3110
3195
  handler: async (args) => {
3111
3196
  const { analyzeInlineHandler } = await import("./tools/hono-inline-analyze.js");
3112
3197
  return await analyzeInlineHandler(args.repo, args.method, args.path);
@@ -3117,9 +3202,9 @@ const TOOL_DEFINITIONS = [
3117
3202
  category: "analysis",
3118
3203
  searchHint: "hono response types status codes error paths RPC client InferResponseType Issue 4270",
3119
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.",
3120
- schema: {
3205
+ schema: lazySchema(() => ({
3121
3206
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3122
- },
3207
+ })),
3123
3208
  handler: async (args) => {
3124
3209
  const { extractResponseTypes } = await import("./tools/hono-response-types.js");
3125
3210
  return await extractResponseTypes(args.repo);
@@ -3130,9 +3215,9 @@ const TOOL_DEFINITIONS = [
3130
3215
  category: "analysis",
3131
3216
  searchHint: "hono modules architecture cluster path prefix middleware bindings enterprise Issue 4121",
3132
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.",
3133
- schema: {
3218
+ schema: lazySchema(() => ({
3134
3219
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3135
- },
3220
+ })),
3136
3221
  handler: async (args) => {
3137
3222
  const { detectHonoModules } = await import("./tools/hono-modules.js");
3138
3223
  return await detectHonoModules(args.repo);
@@ -3143,9 +3228,9 @@ const TOOL_DEFINITIONS = [
3143
3228
  category: "analysis",
3144
3229
  searchHint: "hono dead routes unused RPC client caller refactor monorepo cleanup",
3145
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.",
3146
- schema: {
3231
+ schema: lazySchema(() => ({
3147
3232
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3148
- },
3233
+ })),
3149
3234
  handler: async (args) => {
3150
3235
  const { findDeadHonoRoutes } = await import("./tools/hono-dead-routes.js");
3151
3236
  return await findDeadHonoRoutes(args.repo);
@@ -3157,13 +3242,13 @@ const TOOL_DEFINITIONS = [
3157
3242
  category: "analysis",
3158
3243
  searchHint: "nextjs next.js route map app router pages router rendering strategy SSG SSR ISR edge middleware",
3159
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.",
3160
- schema: {
3245
+ schema: lazySchema(() => ({
3161
3246
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3162
3247
  workspace: z.string().optional().describe("Monorepo workspace path, e.g. 'apps/web'"),
3163
3248
  router: z.enum(["app", "pages", "both"]).optional().describe("Which routers to scan (default 'both')"),
3164
3249
  include_metadata: z.boolean().optional().describe("Include metadata export detection (default true)"),
3165
3250
  max_routes: z.number().int().positive().optional().describe("Max routes to process (default 1000)"),
3166
- },
3251
+ })),
3167
3252
  handler: async (args) => {
3168
3253
  const opts = {};
3169
3254
  if (args.workspace != null)
@@ -3183,11 +3268,11 @@ const TOOL_DEFINITIONS = [
3183
3268
  category: "analysis",
3184
3269
  searchHint: "nextjs seo metadata title description og image audit canonical twitter json-ld",
3185
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.",
3186
- schema: {
3271
+ schema: lazySchema(() => ({
3187
3272
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3188
3273
  workspace: z.string().optional().describe("Monorepo workspace path, e.g. 'apps/web'"),
3189
3274
  max_routes: z.number().int().positive().optional().describe("Max routes to process (default 1000)"),
3190
- },
3275
+ })),
3191
3276
  handler: async (args) => {
3192
3277
  const opts = {};
3193
3278
  if (args.workspace != null)
@@ -3203,13 +3288,13 @@ const TOOL_DEFINITIONS = [
3203
3288
  category: "analysis",
3204
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",
3205
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.",
3206
- schema: {
3291
+ schema: lazySchema(() => ({
3207
3292
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3208
3293
  workspace: z.string().optional().describe("Monorepo workspace path, e.g. 'apps/web'"),
3209
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"),
3210
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"),
3211
3296
  priority_limit: z.number().int().positive().optional().describe("Max findings in priority mode (default: 20)"),
3212
- },
3297
+ })),
3213
3298
  handler: async (args) => {
3214
3299
  const opts = {};
3215
3300
  if (args.workspace != null)
@@ -3230,12 +3315,12 @@ const TOOL_DEFINITIONS = [
3230
3315
  category: "analysis",
3231
3316
  searchHint: "SQL schema ERD entity relationship tables views columns foreign key database migration",
3232
3317
  description: "Analyze SQL schema: tables, views, columns, foreign keys, relationships. Output as JSON or Mermaid ERD.",
3233
- schema: {
3318
+ schema: lazySchema(() => ({
3234
3319
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3235
3320
  file_pattern: z.string().optional().describe("Filter SQL files by pattern (e.g. 'migrations/')"),
3236
3321
  output_format: z.enum(["json", "mermaid"]).optional().describe("Output format (default: json)"),
3237
3322
  include_columns: zBool().describe("Include column details in output (default: true)"),
3238
- },
3323
+ })),
3239
3324
  handler: async (args) => {
3240
3325
  const { analyzeSchema } = await import("./tools/sql-tools.js");
3241
3326
  const opts = {};
@@ -3277,13 +3362,13 @@ const TOOL_DEFINITIONS = [
3277
3362
  category: "analysis",
3278
3363
  searchHint: "SQL table query trace references cross-language ORM Prisma Drizzle migration",
3279
3364
  description: "Trace SQL table references across the codebase: DDL, DML, FK, and ORM models (Prisma, Drizzle).",
3280
- schema: {
3365
+ schema: lazySchema(() => ({
3281
3366
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3282
3367
  table: z.string().describe("Table name to trace (required)"),
3283
3368
  include_orm: zBool().describe("Check Prisma/Drizzle ORM models (default: true)"),
3284
3369
  file_pattern: z.string().optional().describe("Scope search to files matching pattern"),
3285
3370
  max_references: zNum().describe("Maximum references to return (default: 500)"),
3286
- },
3371
+ })),
3287
3372
  handler: async (args) => {
3288
3373
  const { traceQuery } = await import("./tools/sql-tools.js");
3289
3374
  const opts = {
@@ -3324,12 +3409,12 @@ const TOOL_DEFINITIONS = [
3324
3409
  category: "analysis",
3325
3410
  searchHint: "SQL audit composite drift orphan lint DML safety complexity god table schema diagnostic",
3326
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.",
3327
- schema: {
3412
+ schema: lazySchema(() => ({
3328
3413
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3329
3414
  checks: z.array(z.enum(["drift", "orphan", "lint", "dml", "complexity"])).optional().describe("Subset of gates to run (default: all 5)"),
3330
3415
  file_pattern: z.string().optional().describe("Scope to files matching pattern"),
3331
3416
  max_results: zNum().describe("Max DML findings per pattern (default: 200)"),
3332
- },
3417
+ })),
3333
3418
  handler: async (args) => {
3334
3419
  const { sqlAudit } = await import("./tools/sql-tools.js");
3335
3420
  const opts = {};
@@ -3363,10 +3448,10 @@ const TOOL_DEFINITIONS = [
3363
3448
  category: "analysis",
3364
3449
  searchHint: "migration diff SQL destructive DROP ALTER ADD schema change deploy risk",
3365
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.",
3366
- schema: {
3451
+ schema: lazySchema(() => ({
3367
3452
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3368
3453
  file_pattern: z.string().optional().describe("Scope to migration files matching pattern"),
3369
- },
3454
+ })),
3370
3455
  handler: async (args) => {
3371
3456
  const { diffMigrations } = await import("./tools/sql-tools.js");
3372
3457
  const opts = {};
@@ -3398,14 +3483,14 @@ const TOOL_DEFINITIONS = [
3398
3483
  category: "search",
3399
3484
  searchHint: "search column SQL table field name type database schema find",
3400
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.",
3401
- schema: {
3486
+ schema: lazySchema(() => ({
3402
3487
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3403
3488
  query: z.string().describe("Column name substring to match (case-insensitive). Empty = no name filter."),
3404
3489
  type: z.string().optional().describe("Filter by normalized type: int, string, float, bool, datetime, json, uuid, bytes"),
3405
3490
  table: z.string().optional().describe("Filter by table name substring"),
3406
3491
  file_pattern: z.string().optional().describe("Scope to files matching pattern"),
3407
3492
  max_results: zNum().describe("Max columns to return (default: 100)"),
3408
- },
3493
+ })),
3409
3494
  handler: async (args) => {
3410
3495
  const { searchColumns } = await import("./tools/sql-tools.js");
3411
3496
  const opts = {
@@ -3434,10 +3519,10 @@ const TOOL_DEFINITIONS = [
3434
3519
  category: "analysis",
3435
3520
  searchHint: "astro v6 migration upgrade breaking changes compatibility check AM01 AM10 content collections ViewTransitions",
3436
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.",
3437
- schema: {
3522
+ schema: lazySchema(() => ({
3438
3523
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3439
3524
  target_version: z.enum(["6"]).optional().describe("Target Astro version (default: '6')"),
3440
- },
3525
+ })),
3441
3526
  handler: async (args) => {
3442
3527
  const mcArgs = {};
3443
3528
  if (args.repo != null)
@@ -3476,12 +3561,12 @@ const TOOL_DEFINITIONS = [
3476
3561
  category: "discovery",
3477
3562
  searchHint: "plan turn routing recommend tools symbols files gap analysis session aware concierge",
3478
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.",
3479
- schema: {
3564
+ schema: lazySchema(() => ({
3480
3565
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3481
3566
  query: z.string().describe("Natural-language description of what you want to do"),
3482
3567
  max_results: z.number().optional().describe("Max tools to return (default 10)"),
3483
3568
  skip_session: z.boolean().optional().describe("Skip session state checks (default false)"),
3484
- },
3569
+ })),
3485
3570
  handler: async (args) => {
3486
3571
  const { query, max_results, skip_session } = args;
3487
3572
  const opts = {};
@@ -3491,28 +3576,32 @@ const TOOL_DEFINITIONS = [
3491
3576
  opts.skip_session = skip_session;
3492
3577
  const result = await planTurn(args.repo, query, opts);
3493
3578
  for (const name of result.reveal_required) {
3494
- const handle = toolHandles.get(name);
3495
- if (handle && typeof handle.enable === "function") {
3496
- handle.enable();
3497
- }
3579
+ enableToolByName(name);
3498
3580
  }
3499
3581
  return formatPlanTurnResult(result);
3500
3582
  },
3501
3583
  },
3502
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();
3503
3594
  function buildToolSummaries() {
3504
- return TOOL_DEFINITIONS.map((t) => ({
3505
- name: t.name,
3506
- category: t.category,
3507
- description: t.description,
3508
- searchHint: t.searchHint,
3509
- }));
3595
+ return TOOL_SUMMARIES;
3510
3596
  }
3511
3597
  /**
3512
3598
  * Extract structured param info from a ToolDefinition's Zod schema.
3513
3599
  */
3514
3600
  function extractToolParams(def) {
3515
- 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]) => {
3516
3605
  const zodVal = val;
3517
3606
  const isOptional = zodVal.isOptional?.() ?? false;
3518
3607
  return {
@@ -3521,6 +3610,8 @@ function extractToolParams(def) {
3521
3610
  description: zodVal.description ?? "",
3522
3611
  };
3523
3612
  });
3613
+ TOOL_PARAMS_CACHE.set(def.name, params);
3614
+ return params;
3524
3615
  }
3525
3616
  /**
3526
3617
  * Return full param details for a specific list of tool names.
@@ -3531,7 +3622,7 @@ export function describeTools(names) {
3531
3622
  const tools = [];
3532
3623
  const not_found = [];
3533
3624
  for (const name of capped) {
3534
- const def = TOOL_DEFINITIONS.find((t) => t.name === name);
3625
+ const def = TOOL_DEFINITION_MAP.get(name);
3535
3626
  if (!def) {
3536
3627
  not_found.push(name);
3537
3628
  continue;
@@ -3553,7 +3644,7 @@ export function describeTools(names) {
3553
3644
  export function discoverTools(query, category) {
3554
3645
  const summaries = buildToolSummaries();
3555
3646
  const queryTokens = query.toLowerCase().split(/\s+/).filter(Boolean);
3556
- const categories = [...new Set(summaries.map((s) => s.category).filter(Boolean))];
3647
+ const categories = TOOL_CATEGORIES;
3557
3648
  let filtered = summaries;
3558
3649
  if (category) {
3559
3650
  filtered = filtered.filter((s) => s.category === category);
@@ -3580,7 +3671,7 @@ export function discoverTools(query, category) {
3580
3671
  .slice(0, 15)
3581
3672
  .map((s) => {
3582
3673
  // Look up full definition to extract param info for deferred tools
3583
- const fullDef = TOOL_DEFINITIONS.find((t) => t.name === s.tool.name);
3674
+ const fullDef = TOOL_DEFINITION_MAP.get(s.tool.name);
3584
3675
  const params = fullDef
3585
3676
  ? extractToolParams(fullDef).map((p) => `${p.name}${p.required ? "" : "?"}: ${p.description || "string"}`)
3586
3677
  : [];
@@ -3621,22 +3712,16 @@ export function registerTools(server, options) {
3621
3712
  }
3622
3713
  // Clear handles from any previous registration (e.g. tests calling registerTools multiple times)
3623
3714
  toolHandles.clear();
3624
- // Register ALL tools with full schema; store returned handles
3625
- for (const tool of TOOL_DEFINITIONS) {
3626
- const handle = server.tool(tool.name, tool.description, tool.schema, async (args) => wrapTool(tool.name, args, () => tool.handler(args))());
3627
- toolHandles.set(tool.name, handle);
3628
- }
3629
- // Language-gated disabling — tools requiring a language absent from the
3630
- // project are disabled (still registered but hidden from ListTools).
3631
- // 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.
3632
3720
  for (const tool of TOOL_DEFINITIONS) {
3633
- if (!tool.requiresLanguage)
3721
+ if (deferNonCore && !CORE_TOOL_NAMES.has(tool.name)) {
3634
3722
  continue;
3635
- if (languages[tool.requiresLanguage])
3636
- continue;
3637
- const handle = toolHandles.get(tool.name);
3638
- if (handle)
3639
- handle.disable();
3723
+ }
3724
+ registerToolDefinition(server, tool, languages);
3640
3725
  }
3641
3726
  // Always register discover_tools meta-tool
3642
3727
  const discoverHandle = server.tool("discover_tools", "Search tool catalog by keyword or category. Returns matching tools with descriptions.", {
@@ -3654,30 +3739,19 @@ export function registerTools(server, options) {
3654
3739
  const result = describeTools(args.names);
3655
3740
  if (args.reveal === true) {
3656
3741
  for (const t of result.tools) {
3657
- const h = toolHandles.get(t.name);
3658
- if (h)
3659
- h.enable();
3742
+ enableToolByName(t.name);
3660
3743
  }
3661
3744
  }
3662
3745
  return result;
3663
3746
  })());
3664
3747
  toolHandles.set("describe_tools", describeHandle);
3665
- // In deferred mode, disable non-core tools (they remain registered but hidden from ListTools).
3666
- // LLM discovers them via discover_tools, then reveals with describe_tools(reveal: true).
3667
3748
  if (deferNonCore) {
3668
- for (const [name, handle] of toolHandles) {
3669
- if (!CORE_TOOL_NAMES.has(name) && name !== "discover_tools" && name !== "describe_tools") {
3670
- handle.disable();
3671
- }
3672
- }
3673
3749
  // Auto-enable framework-specific tools when project type is detected at CWD.
3674
3750
  // E.g. composer.json → enable PHP/Yii2 tools automatically.
3675
- detectAutoLoadTools(process.cwd())
3751
+ detectAutoLoadToolsCached(process.cwd())
3676
3752
  .then((toEnable) => {
3677
3753
  for (const name of toEnable) {
3678
- const h = toolHandles.get(name);
3679
- if (h)
3680
- h.enable();
3754
+ enableToolByName(name);
3681
3755
  }
3682
3756
  if (toEnable.length > 0) {
3683
3757
  console.error(`[codesift] Auto-loaded ${toEnable.length} framework tools for detected project type: ${toEnable.join(", ")}`);