sparkecoder 0.1.57 → 0.1.59

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.
Files changed (131) hide show
  1. package/dist/agent/index.d.ts +2 -2
  2. package/dist/agent/index.js +706 -68
  3. package/dist/agent/index.js.map +1 -1
  4. package/dist/cli.js +824 -186
  5. package/dist/cli.js.map +1 -1
  6. package/dist/{index-CiS57eVy.d.ts → index-Csad1Nx4.d.ts} +1 -1
  7. package/dist/index.d.ts +3 -3
  8. package/dist/index.js +811 -173
  9. package/dist/index.js.map +1 -1
  10. package/dist/{search-B_PLXXvn.d.ts → search-BETuS1vh.d.ts} +1 -0
  11. package/dist/server/index.js +811 -173
  12. package/dist/server/index.js.map +1 -1
  13. package/dist/tools/index.d.ts +49 -3
  14. package/dist/tools/index.js +659 -60
  15. package/dist/tools/index.js.map +1 -1
  16. package/package.json +1 -1
  17. package/web/.next/BUILD_ID +1 -1
  18. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  19. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  20. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  21. package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  22. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
  23. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  24. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  25. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  26. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  27. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  28. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  29. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  30. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  31. package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  32. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
  34. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  35. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  36. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  37. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  38. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  39. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  40. package/web/.next/standalone/web/.next/server/app/api/config/route.js.nft.json +1 -1
  41. package/web/.next/standalone/web/.next/server/app/api/health/route.js.nft.json +1 -1
  42. package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
  43. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  44. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +2 -2
  45. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +2 -2
  46. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  47. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +2 -2
  48. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
  49. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  50. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  51. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  52. package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
  53. package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
  54. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  55. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +2 -2
  56. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +2 -2
  57. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  58. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +2 -2
  59. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
  60. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  61. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  62. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  63. package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
  64. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  65. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +2 -2
  66. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +2 -2
  67. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  68. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +2 -2
  69. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
  70. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  71. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  72. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  73. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  74. package/web/.next/standalone/web/.next/server/app/docs.rsc +2 -2
  75. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +2 -2
  76. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  77. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +2 -2
  78. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
  79. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  80. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  81. package/web/.next/standalone/web/.next/server/app/embed/[id]/page.js.nft.json +1 -1
  82. package/web/.next/standalone/web/.next/server/app/embed/[id]/page_client-reference-manifest.js +1 -1
  83. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  84. package/web/.next/standalone/web/.next/server/app/index.rsc +2 -2
  85. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
  86. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
  87. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +2 -2
  88. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  89. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
  90. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  91. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_f661d73d._.js → 2374f_076f03ec._.js} +1 -1
  92. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_e35df3d7._.js → 2374f_19289e11._.js} +1 -1
  93. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_6ced4caf._.js → 2374f_2f0d9f6f._.js} +1 -1
  94. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_aade2a6d._.js → 2374f_35475cbe._.js} +1 -1
  95. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_562d2f4f._.js → 2374f_40e35a02._.js} +1 -1
  96. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_05c00b91._.js → 2374f_4666c827._.js} +1 -1
  97. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_ac284eb3._.js → 2374f_4858a1ea._.js} +1 -1
  98. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_39f76acc._.js → 2374f_51385fed._.js} +1 -1
  99. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_ef435bac._.js → 2374f_7db22cde._.js} +1 -1
  100. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_800db4e3._.js → 2374f_90b8e4fb._.js} +1 -1
  101. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_1fa4a79a._.js → 2374f_b17fce11._.js} +1 -1
  102. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_d118a207._.js → 2374f_b4b86c1f._.js} +1 -1
  103. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_934cb548._.js → 2374f_d8b9ce38._.js} +1 -1
  104. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_c272cbb4._.js → 2374f_fb95e3c9._.js} +1 -1
  105. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__f44222d9._.js → [root-of-the-server]__7775f784._.js} +2 -2
  106. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__8cdc7b0b._.js → [root-of-the-server]__bd396152._.js} +4 -4
  107. package/web/.next/standalone/web/.next/server/chunks/ssr/{web_0a13adb9._.js → web_645f4b90._.js} +2 -2
  108. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  109. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  110. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  111. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  112. package/web/.next/standalone/web/.next/static/chunks/2868b007ce5163fc.css +1 -0
  113. package/web/.next/standalone/web/.next/static/chunks/{ca717ddd7c8f37e8.js → 631b023d37a08635.js} +6 -6
  114. package/web/.next/standalone/web/.next/static/static/chunks/2868b007ce5163fc.css +1 -0
  115. package/web/.next/standalone/web/.next/static/static/chunks/{ca717ddd7c8f37e8.js → 631b023d37a08635.js} +6 -6
  116. package/web/.next/standalone/web/src/components/ai-elements/code-graph-tool.tsx +202 -0
  117. package/web/.next/standalone/web/src/components/chat-interface.tsx +49 -0
  118. package/web/.next/static/chunks/2868b007ce5163fc.css +1 -0
  119. package/web/.next/static/chunks/{ca717ddd7c8f37e8.js → 631b023d37a08635.js} +6 -6
  120. package/web/.next/standalone/web/.next/static/chunks/7228b2394d1fb347.css +0 -1
  121. package/web/.next/standalone/web/.next/static/static/chunks/7228b2394d1fb347.css +0 -1
  122. package/web/.next/static/chunks/7228b2394d1fb347.css +0 -1
  123. /package/web/.next/standalone/web/.next/static/{hfhBlcNXMR3DzRN78F_xX → static/u5qqIWWrYpWW_mZUgKKjg}/_buildManifest.js +0 -0
  124. /package/web/.next/standalone/web/.next/static/{hfhBlcNXMR3DzRN78F_xX → static/u5qqIWWrYpWW_mZUgKKjg}/_clientMiddlewareManifest.json +0 -0
  125. /package/web/.next/standalone/web/.next/static/{hfhBlcNXMR3DzRN78F_xX → static/u5qqIWWrYpWW_mZUgKKjg}/_ssgManifest.js +0 -0
  126. /package/web/.next/standalone/web/.next/static/{static/hfhBlcNXMR3DzRN78F_xX → u5qqIWWrYpWW_mZUgKKjg}/_buildManifest.js +0 -0
  127. /package/web/.next/standalone/web/.next/static/{static/hfhBlcNXMR3DzRN78F_xX → u5qqIWWrYpWW_mZUgKKjg}/_clientMiddlewareManifest.json +0 -0
  128. /package/web/.next/standalone/web/.next/static/{static/hfhBlcNXMR3DzRN78F_xX → u5qqIWWrYpWW_mZUgKKjg}/_ssgManifest.js +0 -0
  129. /package/web/.next/static/{hfhBlcNXMR3DzRN78F_xX → u5qqIWWrYpWW_mZUgKKjg}/_buildManifest.js +0 -0
  130. /package/web/.next/static/{hfhBlcNXMR3DzRN78F_xX → u5qqIWWrYpWW_mZUgKKjg}/_clientMiddlewareManifest.json +0 -0
  131. /package/web/.next/static/{hfhBlcNXMR3DzRN78F_xX → u5qqIWWrYpWW_mZUgKKjg}/_ssgManifest.js +0 -0
@@ -637,13 +637,13 @@ var semantic_search_exports = {};
637
637
  __export(semantic_search_exports, {
638
638
  createSemanticSearchTool: () => createSemanticSearchTool
639
639
  });
640
- import { tool as tool7 } from "ai";
641
- import { z as z8 } from "zod";
642
- import { existsSync as existsSync10, readFileSync as readFileSync4 } from "fs";
640
+ import { tool as tool8 } from "ai";
641
+ import { z as z9 } from "zod";
642
+ import { existsSync as existsSync11, readFileSync as readFileSync4 } from "fs";
643
643
  import { join as join4 } from "path";
644
644
  import { minimatch as minimatch3 } from "minimatch";
645
645
  function createSemanticSearchTool(options) {
646
- return tool7({
646
+ return tool8({
647
647
  description: `Search the codebase using semantic similarity. This tool finds code by understanding its meaning, not just matching text.
648
648
 
649
649
  Use this tool when:
@@ -708,7 +708,7 @@ Returns matching code snippets with file paths, line numbers, and relevance scor
708
708
  continue;
709
709
  }
710
710
  const fullPath = join4(options.workingDirectory, filePath);
711
- if (!existsSync10(fullPath)) {
711
+ if (!existsSync11(fullPath)) {
712
712
  continue;
713
713
  }
714
714
  let snippet = "";
@@ -763,11 +763,11 @@ var init_semantic_search = __esm({
763
763
  "use strict";
764
764
  init_semantic();
765
765
  init_config();
766
- semanticSearchInputSchema = z8.object({
767
- query: z8.string().describe("Natural language search query describing what you want to find"),
768
- topK: z8.number().optional().default(10).describe("Number of results to return (default: 10, max: 50)"),
769
- filePattern: z8.string().optional().describe('Filter results by file glob pattern (e.g., "*.ts", "src/**/*.py")'),
770
- language: z8.string().optional().describe('Filter by programming language (e.g., "typescript", "python")')
766
+ semanticSearchInputSchema = z9.object({
767
+ query: z9.string().describe("Natural language search query describing what you want to find"),
768
+ topK: z9.number().optional().default(10).describe("Number of results to return (default: 10, max: 50)"),
769
+ filePattern: z9.string().optional().describe('Filter results by file glob pattern (e.g., "*.ts", "src/**/*.py")'),
770
+ language: z9.string().optional().describe('Filter by programming language (e.g., "typescript", "python")')
771
771
  });
772
772
  }
773
773
  });
@@ -1670,7 +1670,11 @@ async function createClient(serverId, handle, root) {
1670
1670
  dynamicRegistration: true
1671
1671
  },
1672
1672
  documentSymbol: {
1673
- dynamicRegistration: true
1673
+ dynamicRegistration: true,
1674
+ hierarchicalDocumentSymbolSupport: true,
1675
+ symbolKind: {
1676
+ valueSet: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]
1677
+ }
1674
1678
  }
1675
1679
  },
1676
1680
  workspace: {
@@ -1767,7 +1771,7 @@ async function createClient(serverId, handle, root) {
1767
1771
  },
1768
1772
  async waitForDiagnostics(filePath, timeoutMs = 5e3) {
1769
1773
  const normalized = normalizePath(filePath);
1770
- return new Promise((resolve9) => {
1774
+ return new Promise((resolve10) => {
1771
1775
  const startTime = Date.now();
1772
1776
  let debounceTimer;
1773
1777
  let resolved = false;
@@ -1786,7 +1790,7 @@ async function createClient(serverId, handle, root) {
1786
1790
  if (resolved) return;
1787
1791
  resolved = true;
1788
1792
  cleanup();
1789
- resolve9(diagnostics.get(normalized) || []);
1793
+ resolve10(diagnostics.get(normalized) || []);
1790
1794
  };
1791
1795
  const onDiagnostic = () => {
1792
1796
  if (debounceTimer) clearTimeout(debounceTimer);
@@ -1812,6 +1816,100 @@ async function createClient(serverId, handle, root) {
1812
1816
  getAllDiagnostics() {
1813
1817
  return new Map(diagnostics);
1814
1818
  },
1819
+ async getDefinition(filePath, line, character) {
1820
+ const normalized = normalizePath(filePath);
1821
+ if (!fileVersions.has(normalized)) {
1822
+ await client.notifyOpen(normalized);
1823
+ }
1824
+ try {
1825
+ const result = await connection.sendRequest("textDocument/definition", {
1826
+ textDocument: { uri: pathToFileURL(normalized).href },
1827
+ position: { line, character }
1828
+ });
1829
+ if (!result) return [];
1830
+ const items = Array.isArray(result) ? result : [result];
1831
+ return items.map((r) => ({
1832
+ uri: r.targetUri || r.uri,
1833
+ range: r.targetRange || r.range
1834
+ }));
1835
+ } catch (error) {
1836
+ console.error("[lsp] Error getting definition:", error);
1837
+ return [];
1838
+ }
1839
+ },
1840
+ async getReferences(filePath, line, character, includeDeclaration = false) {
1841
+ const normalized = normalizePath(filePath);
1842
+ if (!fileVersions.has(normalized)) {
1843
+ await client.notifyOpen(normalized);
1844
+ }
1845
+ try {
1846
+ const result = await connection.sendRequest("textDocument/references", {
1847
+ textDocument: { uri: pathToFileURL(normalized).href },
1848
+ position: { line, character },
1849
+ context: { includeDeclaration }
1850
+ });
1851
+ return result || [];
1852
+ } catch (error) {
1853
+ console.error("[lsp] Error getting references:", error);
1854
+ return [];
1855
+ }
1856
+ },
1857
+ async getHover(filePath, line, character) {
1858
+ const normalized = normalizePath(filePath);
1859
+ if (!fileVersions.has(normalized)) {
1860
+ await client.notifyOpen(normalized);
1861
+ }
1862
+ try {
1863
+ const result = await connection.sendRequest("textDocument/hover", {
1864
+ textDocument: { uri: pathToFileURL(normalized).href },
1865
+ position: { line, character }
1866
+ });
1867
+ if (!result || !result.contents) return null;
1868
+ if (typeof result.contents === "string") return result.contents;
1869
+ if (result.contents.value) return result.contents.value;
1870
+ if (Array.isArray(result.contents)) {
1871
+ return result.contents.map((c) => typeof c === "string" ? c : c.value).join("\n");
1872
+ }
1873
+ return null;
1874
+ } catch (error) {
1875
+ console.error("[lsp] Error getting hover:", error);
1876
+ return null;
1877
+ }
1878
+ },
1879
+ async getDocumentSymbols(filePath) {
1880
+ const normalized = normalizePath(filePath);
1881
+ if (!fileVersions.has(normalized)) {
1882
+ await client.notifyOpen(normalized);
1883
+ }
1884
+ try {
1885
+ const result = await connection.sendRequest("textDocument/documentSymbol", {
1886
+ textDocument: { uri: pathToFileURL(normalized).href }
1887
+ });
1888
+ if (!result || result.length === 0) return [];
1889
+ if (result[0].range) {
1890
+ return result;
1891
+ }
1892
+ return result.map((si) => ({
1893
+ name: si.name,
1894
+ kind: si.kind,
1895
+ range: si.location?.range ?? { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } },
1896
+ selectionRange: si.location?.range ?? { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } },
1897
+ detail: si.containerName
1898
+ }));
1899
+ } catch (error) {
1900
+ console.error("[lsp] Error getting document symbols:", error);
1901
+ return [];
1902
+ }
1903
+ },
1904
+ async findWorkspaceSymbols(query) {
1905
+ try {
1906
+ const result = await connection.sendRequest("workspace/symbol", { query });
1907
+ return result || [];
1908
+ } catch (error) {
1909
+ console.error("[lsp] Error finding workspace symbols:", error);
1910
+ return [];
1911
+ }
1912
+ },
1815
1913
  async shutdown() {
1816
1914
  try {
1817
1915
  await connection.sendRequest("shutdown");
@@ -1934,6 +2032,24 @@ async function getAllDiagnostics() {
1934
2032
  }
1935
2033
  return results;
1936
2034
  }
2035
+ async function getReferences(filePath, line, character, includeDeclaration = false) {
2036
+ const normalized = normalizePath(filePath);
2037
+ const client = await getClientForFile(normalized);
2038
+ if (!client) return [];
2039
+ return client.getReferences(normalized, line, character, includeDeclaration);
2040
+ }
2041
+ async function getHover(filePath, line, character) {
2042
+ const normalized = normalizePath(filePath);
2043
+ const client = await getClientForFile(normalized);
2044
+ if (!client) return null;
2045
+ return client.getHover(normalized, line, character);
2046
+ }
2047
+ async function getDocumentSymbols(filePath) {
2048
+ const normalized = normalizePath(filePath);
2049
+ const client = await getClientForFile(normalized);
2050
+ if (!client) return [];
2051
+ return client.getDocumentSymbols(normalized);
2052
+ }
1937
2053
  async function formatDiagnosticsOutput(filePath, options = {}) {
1938
2054
  const diagnostics = await getDiagnostics(filePath);
1939
2055
  return formatDiagnosticsForAgent(filePath, diagnostics, options);
@@ -2029,7 +2145,7 @@ Working directory: ${options.workingDirectory}`,
2029
2145
  isChunked: true
2030
2146
  });
2031
2147
  if (chunkCount > 1) {
2032
- await new Promise((resolve9) => setTimeout(resolve9, 0));
2148
+ await new Promise((resolve10) => setTimeout(resolve10, 0));
2033
2149
  }
2034
2150
  }
2035
2151
  }
@@ -2736,8 +2852,8 @@ ${file.relativePath}:`);
2736
2852
  }
2737
2853
 
2738
2854
  // src/tools/search.ts
2739
- import { tool as tool9 } from "ai";
2740
- import { z as z10 } from "zod";
2855
+ import { tool as tool10 } from "ai";
2856
+ import { z as z11 } from "zod";
2741
2857
 
2742
2858
  // src/agent/subagent.ts
2743
2859
  import {
@@ -2922,8 +3038,8 @@ var Subagent = class {
2922
3038
  if (eventQueue.length > 0) {
2923
3039
  yield eventQueue.shift();
2924
3040
  } else if (!done) {
2925
- const event = await new Promise((resolve9) => {
2926
- resolveNext = resolve9;
3041
+ const event = await new Promise((resolve10) => {
3042
+ resolveNext = resolve10;
2927
3043
  });
2928
3044
  if (event) {
2929
3045
  yield event;
@@ -2935,14 +3051,466 @@ var Subagent = class {
2935
3051
  };
2936
3052
 
2937
3053
  // src/agent/subagents/search.ts
2938
- import { tool as tool8 } from "ai";
2939
- import { z as z9 } from "zod";
3054
+ import { tool as tool9 } from "ai";
3055
+ import { z as z10 } from "zod";
2940
3056
  import { exec as exec4 } from "child_process";
2941
3057
  import { promisify as promisify4 } from "util";
2942
- import { readFile as readFile7, stat as stat3, readdir as readdir3 } from "fs/promises";
2943
- import { resolve as resolve8, relative as relative7, isAbsolute as isAbsolute4 } from "path";
2944
- import { existsSync as existsSync11 } from "fs";
3058
+ import { readFile as readFile8, stat as stat3, readdir as readdir4 } from "fs/promises";
3059
+ import { resolve as resolve9, relative as relative8, isAbsolute as isAbsolute5 } from "path";
3060
+ import { existsSync as existsSync12 } from "fs";
2945
3061
  init_semantic();
3062
+
3063
+ // src/tools/code-graph.ts
3064
+ import { tool as tool7 } from "ai";
3065
+ import { z as z8 } from "zod";
3066
+ import { resolve as resolve8, relative as relative7, isAbsolute as isAbsolute4, basename as basename3 } from "path";
3067
+ import { readFile as readFile7, readdir as readdir3 } from "fs/promises";
3068
+ import { existsSync as existsSync10 } from "fs";
3069
+ import { fileURLToPath as fileURLToPath2 } from "url";
3070
+ import { execFileSync } from "child_process";
3071
+ var codeGraphInputSchema = z8.object({
3072
+ symbol: z8.string().describe(
3073
+ "The symbol name to inspect (function, component, class, type, variable, etc.)"
3074
+ ),
3075
+ filePath: z8.string().optional().describe(
3076
+ "File path where the symbol is defined. If omitted, searches the workspace via grep."
3077
+ ),
3078
+ depth: z8.number().optional().default(2).describe(
3079
+ "How many levels of references to traverse upward (default: 2, max: 3). Level 1 = direct usages, level 2 = usages of those usages."
3080
+ )
3081
+ });
3082
+ function isPageFile(filePath) {
3083
+ const normalized = filePath.replace(/\\/g, "/");
3084
+ if (/\/app\/(.+\/)?(page|layout|loading|error|not-found)\.(tsx?|jsx?)$/.test(normalized)) return true;
3085
+ if (/\/pages\/(?!_|api\/).+\.(tsx?|jsx?)$/.test(normalized)) return true;
3086
+ return false;
3087
+ }
3088
+ function extractRoutePath(filePath, workingDirectory) {
3089
+ const rel = relative7(workingDirectory, filePath).replace(/\\/g, "/");
3090
+ const appMatch = rel.match(/(?:src\/)?app((?:\/[^/]+)*?)\/(?:page|layout|loading|error|not-found)\.\w+$/);
3091
+ if (appMatch) return appMatch[1] || "/";
3092
+ const pagesMatch = rel.match(/(?:src\/)?pages(\/.*?)(?:\/index)?\.\w+$/);
3093
+ if (pagesMatch) return pagesMatch[1] || "/";
3094
+ return void 0;
3095
+ }
3096
+ function symbolKindName(kind) {
3097
+ const names = {
3098
+ [5 /* Class */]: "class",
3099
+ [12 /* Function */]: "function",
3100
+ [6 /* Method */]: "method",
3101
+ [7 /* Property */]: "property",
3102
+ [13 /* Variable */]: "variable",
3103
+ [11 /* Interface */]: "interface",
3104
+ [10 /* Enum */]: "enum",
3105
+ [14 /* Constant */]: "constant",
3106
+ [9 /* Constructor */]: "constructor",
3107
+ [2 /* Module */]: "module",
3108
+ [3 /* Namespace */]: "namespace",
3109
+ [26 /* TypeParameter */]: "type_param",
3110
+ [8 /* Field */]: "field",
3111
+ [22 /* EnumMember */]: "enum_member",
3112
+ [19 /* Object */]: "object"
3113
+ };
3114
+ return names[kind] || "symbol";
3115
+ }
3116
+ function findContainingSymbol(symbols, line, character) {
3117
+ for (const sym of symbols) {
3118
+ if (!sym.range) continue;
3119
+ const { start, end } = sym.range;
3120
+ const afterStart = line > start.line || line === start.line && character >= start.character;
3121
+ const beforeEnd = line < end.line || line === end.line && character < end.character;
3122
+ if (afterStart && beforeEnd) {
3123
+ if (sym.children?.length) {
3124
+ const child = findContainingSymbol(sym.children, line, character);
3125
+ if (child) return child;
3126
+ }
3127
+ return sym;
3128
+ }
3129
+ }
3130
+ return null;
3131
+ }
3132
+ function findSymbolByName(symbols, name) {
3133
+ for (const sym of symbols) {
3134
+ if (sym.name === name && sym.selectionRange) return sym;
3135
+ if (sym.children) {
3136
+ const found = findSymbolByName(sym.children, name);
3137
+ if (found) return found;
3138
+ }
3139
+ }
3140
+ return null;
3141
+ }
3142
+ function cleanHoverText(text) {
3143
+ return text.replace(/```\w*\n?/g, "").replace(/\n```/g, "").trim();
3144
+ }
3145
+ async function grepForSymbol(symbol, workingDirectory) {
3146
+ const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3147
+ const rgPatterns = [
3148
+ `(export\\s+)?(default\\s+)?(function|const|let|var|class|interface|type|enum)\\s+${escaped}\\b`,
3149
+ `(export\\s+)?(default\\s+)?\\b${escaped}\\s*[=:(]`
3150
+ ];
3151
+ for (const pattern of rgPatterns) {
3152
+ try {
3153
+ const result = execFileSync("rg", [
3154
+ "-n",
3155
+ "--no-heading",
3156
+ "-e",
3157
+ pattern,
3158
+ "--glob",
3159
+ "*.{ts,tsx,js,jsx}",
3160
+ "-m",
3161
+ "5"
3162
+ ], {
3163
+ cwd: workingDirectory,
3164
+ encoding: "utf-8",
3165
+ timeout: 5e3,
3166
+ stdio: ["pipe", "pipe", "pipe"]
3167
+ }).trim();
3168
+ if (result) {
3169
+ const firstLine = result.split("\n")[0];
3170
+ const match = firstLine.match(/^(.+?):(\d+):(.*)/);
3171
+ if (match) {
3172
+ const col = match[3].indexOf(symbol);
3173
+ return {
3174
+ filePath: resolve8(workingDirectory, match[1]),
3175
+ line: parseInt(match[2]) - 1,
3176
+ char: col >= 0 ? col : 0
3177
+ };
3178
+ }
3179
+ }
3180
+ } catch {
3181
+ }
3182
+ }
3183
+ const defPattern = new RegExp(
3184
+ `(export|function|const|let|var|class|interface|type|enum)\\s+.*\\b${escaped}\\b`
3185
+ );
3186
+ const SUPPORTED_EXTS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
3187
+ const IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "coverage"]);
3188
+ async function search(dir, maxFiles) {
3189
+ if (maxFiles <= 0) return null;
3190
+ let remaining = maxFiles;
3191
+ try {
3192
+ const entries = await readdir3(dir, { withFileTypes: true });
3193
+ for (const entry of entries) {
3194
+ if (remaining <= 0) return null;
3195
+ const fullPath = resolve8(dir, entry.name);
3196
+ if (entry.isDirectory()) {
3197
+ if (IGNORED_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
3198
+ const found = await search(fullPath, remaining);
3199
+ if (found) return found;
3200
+ remaining -= 10;
3201
+ } else if (entry.isFile()) {
3202
+ const ext = entry.name.substring(entry.name.lastIndexOf("."));
3203
+ if (!SUPPORTED_EXTS.has(ext)) continue;
3204
+ remaining--;
3205
+ const content = await readFile7(fullPath, "utf-8");
3206
+ const lines = content.split("\n");
3207
+ for (let i = 0; i < lines.length; i++) {
3208
+ if (defPattern.test(lines[i])) {
3209
+ const col = lines[i].indexOf(symbol);
3210
+ if (col >= 0) {
3211
+ return { filePath: fullPath, line: i, char: col };
3212
+ }
3213
+ }
3214
+ }
3215
+ }
3216
+ }
3217
+ } catch {
3218
+ }
3219
+ return null;
3220
+ }
3221
+ return search(workingDirectory, 200);
3222
+ }
3223
+ var MAX_REF_FILES = 15;
3224
+ var MAX_LEVEL2_PARENTS = 8;
3225
+ var MAX_LEVEL2_SYMBOLS_PER_PARENT = 3;
3226
+ function createCodeGraphTool(options) {
3227
+ return tool7({
3228
+ description: `Inspect a symbol's type information and usage graph using the TypeScript language server.
3229
+
3230
+ Given a symbol name (function, component, class, type, etc.), this tool will:
3231
+ 1. Find its definition and full type signature (parameters, return type)
3232
+ 2. Find all references \u2014 what components/functions/files use this symbol
3233
+ 3. Identify which pages/routes contain it in their component tree
3234
+ 4. Show the file's symbol structure for surrounding context
3235
+
3236
+ Use this to understand:
3237
+ - Component hierarchies (what renders what, which pages are affected)
3238
+ - Type signatures and parameter/return types before making changes
3239
+ - How deeply a symbol is used across the codebase
3240
+ - What will break if you change something
3241
+
3242
+ Supports TypeScript, JavaScript, TSX, JSX files.
3243
+ Working directory: ${options.workingDirectory}`,
3244
+ inputSchema: codeGraphInputSchema,
3245
+ execute: async ({ symbol, filePath, depth }) => {
3246
+ const maxDepth = Math.min(depth ?? 2, 3);
3247
+ try {
3248
+ let defFilePath;
3249
+ let defLine = 0;
3250
+ let defChar = 0;
3251
+ let defSymbol = null;
3252
+ if (filePath) {
3253
+ const absPath = isAbsolute4(filePath) ? filePath : resolve8(options.workingDirectory, filePath);
3254
+ if (!existsSync10(absPath)) {
3255
+ return { success: false, error: `File not found: ${filePath}` };
3256
+ }
3257
+ if (!isSupported(absPath)) {
3258
+ return { success: false, error: `File type not supported. Supports: ${getSupportedExtensions().join(", ")}` };
3259
+ }
3260
+ await touchFile(absPath, true);
3261
+ const symbols = await getDocumentSymbols(absPath);
3262
+ defSymbol = findSymbolByName(symbols, symbol);
3263
+ if (defSymbol) {
3264
+ defFilePath = absPath;
3265
+ defLine = defSymbol.selectionRange.start.line;
3266
+ defChar = defSymbol.selectionRange.start.character;
3267
+ } else {
3268
+ const content = await readFile7(absPath, "utf-8");
3269
+ const lines2 = content.split("\n");
3270
+ const defPattern = new RegExp(
3271
+ `(export|function|const|let|var|class|interface|type|enum)\\s+.*\\b${symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`
3272
+ );
3273
+ for (let i = 0; i < lines2.length; i++) {
3274
+ if (defPattern.test(lines2[i])) {
3275
+ const col = lines2[i].indexOf(symbol);
3276
+ if (col !== -1) {
3277
+ defFilePath = absPath;
3278
+ defLine = i;
3279
+ defChar = col;
3280
+ break;
3281
+ }
3282
+ }
3283
+ }
3284
+ if (!defFilePath) {
3285
+ for (let i = 0; i < lines2.length; i++) {
3286
+ const col = lines2[i].indexOf(symbol);
3287
+ if (col !== -1) {
3288
+ defFilePath = absPath;
3289
+ defLine = i;
3290
+ defChar = col;
3291
+ break;
3292
+ }
3293
+ }
3294
+ }
3295
+ }
3296
+ } else {
3297
+ const found = await grepForSymbol(symbol, options.workingDirectory);
3298
+ if (found) {
3299
+ defFilePath = found.filePath;
3300
+ defLine = found.line;
3301
+ defChar = found.char;
3302
+ }
3303
+ }
3304
+ if (!defFilePath) {
3305
+ return {
3306
+ success: false,
3307
+ error: `Could not find symbol "${symbol}" in the codebase. Try providing a filePath.`
3308
+ };
3309
+ }
3310
+ await touchFile(defFilePath, true);
3311
+ const rawHover = await getHover(defFilePath, defLine, defChar);
3312
+ const typeInfo = rawHover ? cleanHoverText(rawHover) : null;
3313
+ const fileSymbols = await getDocumentSymbols(defFilePath);
3314
+ if (!defSymbol && fileSymbols.length > 0) {
3315
+ defSymbol = findSymbolByName(fileSymbols, symbol);
3316
+ }
3317
+ const references = await getReferences(defFilePath, defLine, defChar, false);
3318
+ const refsByFile = /* @__PURE__ */ new Map();
3319
+ for (const ref of references) {
3320
+ const refPath = fileURLToPath2(ref.uri);
3321
+ if (!refsByFile.has(refPath)) {
3322
+ refsByFile.set(refPath, []);
3323
+ }
3324
+ refsByFile.get(refPath).push(ref);
3325
+ }
3326
+ const refFileInfos = [];
3327
+ let processed = 0;
3328
+ for (const [refPath, locs] of refsByFile) {
3329
+ if (processed >= MAX_REF_FILES) break;
3330
+ if (refPath === defFilePath) continue;
3331
+ processed++;
3332
+ const relPath = relative7(options.workingDirectory, refPath);
3333
+ const pageFile = isPageFile(refPath);
3334
+ const routePath = pageFile ? extractRoutePath(refPath, options.workingDirectory) : void 0;
3335
+ await touchFile(refPath, false);
3336
+ const refFileSymbols = await getDocumentSymbols(refPath);
3337
+ const seen = /* @__PURE__ */ new Map();
3338
+ for (const loc of locs) {
3339
+ const container = findContainingSymbol(
3340
+ refFileSymbols,
3341
+ loc.range.start.line,
3342
+ loc.range.start.character
3343
+ );
3344
+ if (container && !seen.has(container.name)) {
3345
+ let containerHover = null;
3346
+ try {
3347
+ const raw = await getHover(
3348
+ refPath,
3349
+ container.selectionRange.start.line,
3350
+ container.selectionRange.start.character
3351
+ );
3352
+ if (raw) containerHover = cleanHoverText(raw).split("\n")[0];
3353
+ } catch {
3354
+ }
3355
+ seen.set(container.name, {
3356
+ name: container.name,
3357
+ kind: symbolKindName(container.kind),
3358
+ line: container.selectionRange.start.line + 1,
3359
+ char: container.selectionRange.start.character,
3360
+ typeInfo: containerHover || void 0
3361
+ });
3362
+ }
3363
+ }
3364
+ refFileInfos.push({
3365
+ filePath: refPath,
3366
+ relativePath: relPath,
3367
+ isPage: pageFile,
3368
+ routePath,
3369
+ containingSymbols: Array.from(seen.values())
3370
+ });
3371
+ }
3372
+ const level2Refs = [];
3373
+ if (maxDepth >= 2) {
3374
+ for (const refFile of refFileInfos.slice(0, MAX_LEVEL2_PARENTS)) {
3375
+ for (const sym of refFile.containingSymbols.slice(0, MAX_LEVEL2_SYMBOLS_PER_PARENT)) {
3376
+ try {
3377
+ const symLineIdx = sym.line - 1;
3378
+ const symChar = sym.char;
3379
+ const l2Locations = await getReferences(
3380
+ refFile.filePath,
3381
+ symLineIdx,
3382
+ symChar,
3383
+ false
3384
+ );
3385
+ const l2Nodes = [];
3386
+ const seenPaths = /* @__PURE__ */ new Set();
3387
+ for (const loc of l2Locations.slice(0, 10)) {
3388
+ const l2Path = fileURLToPath2(loc.uri);
3389
+ if (l2Path === refFile.filePath || l2Path === defFilePath) continue;
3390
+ if (seenPaths.has(l2Path)) continue;
3391
+ seenPaths.add(l2Path);
3392
+ const l2Rel = relative7(options.workingDirectory, l2Path);
3393
+ const l2Page = isPageFile(l2Path);
3394
+ const l2Route = l2Page ? extractRoutePath(l2Path, options.workingDirectory) : void 0;
3395
+ let containerName;
3396
+ try {
3397
+ await touchFile(l2Path, false);
3398
+ const l2Symbols = await getDocumentSymbols(l2Path);
3399
+ const container = findContainingSymbol(l2Symbols, loc.range.start.line, loc.range.start.character);
3400
+ if (container) containerName = container.name;
3401
+ } catch {
3402
+ }
3403
+ l2Nodes.push({
3404
+ relativePath: l2Rel,
3405
+ isPage: l2Page,
3406
+ routePath: l2Route,
3407
+ containingSymbol: containerName
3408
+ });
3409
+ }
3410
+ if (l2Nodes.length > 0) {
3411
+ level2Refs.push({
3412
+ parentSymbol: sym.name,
3413
+ parentFile: refFile.relativePath,
3414
+ refs: l2Nodes
3415
+ });
3416
+ }
3417
+ } catch {
3418
+ }
3419
+ }
3420
+ }
3421
+ }
3422
+ const relDefPath = relative7(options.workingDirectory, defFilePath);
3423
+ const lines = [];
3424
+ lines.push(`=== ${symbol} ===`);
3425
+ lines.push(`File: ${relDefPath}:${defLine + 1}`);
3426
+ if (defSymbol) lines.push(`Kind: ${symbolKindName(defSymbol.kind)}`);
3427
+ if (typeInfo) lines.push(`Type: ${typeInfo}`);
3428
+ const externalRefCount = references.filter((r) => fileURLToPath2(r.uri) !== defFilePath).length;
3429
+ const externalFileCount = refsByFile.size - (refsByFile.has(defFilePath) ? 1 : 0);
3430
+ if (refFileInfos.length > 0) {
3431
+ lines.push("");
3432
+ lines.push(`=== Referenced by (${externalRefCount} usages across ${externalFileCount} files) ===`);
3433
+ const pages = refFileInfos.filter((r) => r.isPage);
3434
+ const nonPages = refFileInfos.filter((r) => !r.isPage);
3435
+ if (pages.length > 0) {
3436
+ lines.push("");
3437
+ lines.push("Pages/Routes:");
3438
+ for (const page of pages) {
3439
+ lines.push(` ${page.relativePath}${page.routePath ? ` \u2192 ${page.routePath}` : ""}`);
3440
+ for (const s of page.containingSymbols) {
3441
+ lines.push(` \u2514\u2500\u2500 ${s.name} (${s.kind}:${s.line})${s.typeInfo ? ` \u2014 ${s.typeInfo}` : ""}`);
3442
+ }
3443
+ }
3444
+ }
3445
+ if (nonPages.length > 0) {
3446
+ lines.push("");
3447
+ lines.push("Components/Functions:");
3448
+ for (const ref of nonPages) {
3449
+ lines.push(` ${ref.relativePath}`);
3450
+ for (const s of ref.containingSymbols) {
3451
+ const typePart = s.typeInfo && s.typeInfo.length < 120 ? ` \u2014 ${s.typeInfo}` : "";
3452
+ lines.push(` \u2514\u2500\u2500 ${s.name} (${s.kind}:${s.line})${typePart}`);
3453
+ }
3454
+ }
3455
+ }
3456
+ } else {
3457
+ lines.push("");
3458
+ lines.push("No external references found (symbol may be unused or only used within the same file).");
3459
+ }
3460
+ if (level2Refs.length > 0) {
3461
+ lines.push("");
3462
+ lines.push("=== Extended tree (level 2) ===");
3463
+ for (const l2 of level2Refs) {
3464
+ lines.push("");
3465
+ lines.push(`${l2.parentSymbol} (${l2.parentFile}) is used by:`);
3466
+ for (const ref of l2.refs) {
3467
+ const tag = ref.isPage ? " [PAGE]" : "";
3468
+ const route = ref.routePath ? ` \u2192 ${ref.routePath}` : "";
3469
+ const container = ref.containingSymbol ? ` in ${ref.containingSymbol}` : "";
3470
+ lines.push(` \u2514\u2500\u2500 ${ref.relativePath}${tag}${route}${container}`);
3471
+ }
3472
+ }
3473
+ }
3474
+ if (fileSymbols.length > 0) {
3475
+ lines.push("");
3476
+ lines.push(`=== File structure (${basename3(defFilePath)}) ===`);
3477
+ for (const sym of fileSymbols) {
3478
+ const marker = sym.name === symbol ? " \u2190 target" : "";
3479
+ lines.push(` ${sym.name} (${symbolKindName(sym.kind)}:${sym.selectionRange.start.line + 1})${marker}`);
3480
+ if (sym.children) {
3481
+ for (const child of sym.children.slice(0, 10)) {
3482
+ lines.push(` \u2514\u2500\u2500 ${child.name} (${symbolKindName(child.kind)}:${child.selectionRange.start.line + 1})`);
3483
+ }
3484
+ if (sym.children.length > 10) {
3485
+ lines.push(` ... and ${sym.children.length - 10} more`);
3486
+ }
3487
+ }
3488
+ }
3489
+ }
3490
+ const formattedResult = lines.join("\n");
3491
+ return {
3492
+ success: true,
3493
+ symbol,
3494
+ filePath: relDefPath,
3495
+ line: defLine + 1,
3496
+ kind: defSymbol ? symbolKindName(defSymbol.kind) : void 0,
3497
+ typeInfo: typeInfo || void 0,
3498
+ referenceCount: externalRefCount,
3499
+ referenceFiles: externalFileCount,
3500
+ pages: refFileInfos.filter((r) => r.isPage).map((r) => ({ path: r.relativePath, route: r.routePath })),
3501
+ formattedResult
3502
+ };
3503
+ } catch (error) {
3504
+ return {
3505
+ success: false,
3506
+ error: error instanceof Error ? error.message : String(error)
3507
+ };
3508
+ }
3509
+ }
3510
+ });
3511
+ }
3512
+
3513
+ // src/agent/subagents/search.ts
2946
3514
  var execAsync4 = promisify4(exec4);
2947
3515
  var MAX_OUTPUT_CHARS4 = 1e4;
2948
3516
  var MAX_FILE_SIZE3 = 1 * 1024 * 1024;
@@ -2972,17 +3540,20 @@ ${contextBlock}
2972
3540
  - **glob**: Find files matching a name pattern. Best for file discovery.
2973
3541
  - **read_file**: Read contents of a specific file. Use to examine code found in searches.
2974
3542
  - **list_dir**: List directory contents. Use to understand project structure.
3543
+ - **code_graph**: Inspect a symbol's type hierarchy, references, and usage graph via the TypeScript language server. Returns type signatures, all files that reference the symbol, and which pages/routes contain it. Best for understanding component/function relationships and impact analysis.
2975
3544
 
2976
3545
  ## Search Strategy
2977
3546
 
2978
3547
  1. **Start with semantic_search** if available - it finds code by meaning, which is the fastest way to explore
2979
3548
  2. **Use grep** for exact symbol/string matches (function names, class names, imports)
2980
- 3. **Use glob** for file discovery by name patterns
2981
- 4. **Read key files** to get actual code content and understand context
2982
- 5. **Run searches in PARALLEL** - make multiple tool calls at once to cover different angles simultaneously. This is critical for speed.
3549
+ 3. **Use code_graph** when you need to understand a symbol's type signature, what depends on it, or which pages use it. It's much more precise than grep for understanding relationships.
3550
+ 4. **Use glob** for file discovery by name patterns
3551
+ 5. **Read key files** to get actual code content and understand context
3552
+ 6. **Run searches in PARALLEL** - make multiple tool calls at once to cover different angles simultaneously. This is critical for speed.
2983
3553
 
2984
3554
  ### Tool Selection Guide
2985
3555
  - Know the exact name? Use **grep** (e.g. \`getUserById\`, \`class AuthService\`)
3556
+ - Need type info, references, or impact analysis? Use **code_graph** (e.g. \`code_graph({ symbol: "UserCard" })\`)
2986
3557
  - Exploring a concept? Use **semantic_search** (e.g. "how does authentication work")
2987
3558
  - Looking for files? Use **glob** (e.g. \`**/*.config.ts\`, \`**/auth/**\`)
2988
3559
  - Need file content? Use **read_file** with optional line ranges for large files
@@ -3019,17 +3590,17 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
3019
3590
  async getToolsAsync(options) {
3020
3591
  const workingDirectory = options.workingDirectory;
3021
3592
  const tools = {
3022
- grep: tool8({
3593
+ grep: tool9({
3023
3594
  description: "Search for patterns in files using ripgrep. Returns matching lines with file paths and line numbers.",
3024
- inputSchema: z9.object({
3025
- pattern: z9.string().describe("The regex pattern to search for"),
3026
- path: z9.string().optional().describe("Subdirectory or file to search in (relative to working directory)"),
3027
- fileType: z9.string().optional().describe('File type to filter (e.g., "ts", "js", "py")'),
3028
- maxResults: z9.number().optional().default(50).describe("Maximum number of results to return")
3595
+ inputSchema: z10.object({
3596
+ pattern: z10.string().describe("The regex pattern to search for"),
3597
+ path: z10.string().optional().describe("Subdirectory or file to search in (relative to working directory)"),
3598
+ fileType: z10.string().optional().describe('File type to filter (e.g., "ts", "js", "py")'),
3599
+ maxResults: z10.number().optional().default(50).describe("Maximum number of results to return")
3029
3600
  }),
3030
3601
  execute: async ({ pattern, path, fileType, maxResults }) => {
3031
3602
  try {
3032
- const searchPath = path ? resolve8(workingDirectory, path) : workingDirectory;
3603
+ const searchPath = path ? resolve9(workingDirectory, path) : workingDirectory;
3033
3604
  let args = ["rg", "--line-number", "--no-heading"];
3034
3605
  if (fileType) {
3035
3606
  args.push("--type", fileType);
@@ -3066,11 +3637,11 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
3066
3637
  }
3067
3638
  }
3068
3639
  }),
3069
- glob: tool8({
3640
+ glob: tool9({
3070
3641
  description: "Find files matching a glob pattern. Returns list of matching file paths.",
3071
- inputSchema: z9.object({
3072
- pattern: z9.string().describe('Glob pattern (e.g., "**/*.ts", "src/**/*.tsx", "*.json")'),
3073
- maxResults: z9.number().optional().default(100).describe("Maximum number of files to return")
3642
+ inputSchema: z10.object({
3643
+ pattern: z10.string().describe('Glob pattern (e.g., "**/*.ts", "src/**/*.tsx", "*.json")'),
3644
+ maxResults: z10.number().optional().default(100).describe("Maximum number of files to return")
3074
3645
  }),
3075
3646
  execute: async ({ pattern, maxResults }) => {
3076
3647
  try {
@@ -3097,17 +3668,17 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
3097
3668
  }
3098
3669
  }
3099
3670
  }),
3100
- read_file: tool8({
3671
+ read_file: tool9({
3101
3672
  description: "Read the contents of a file. Use this to examine specific files found in search.",
3102
- inputSchema: z9.object({
3103
- path: z9.string().describe("Path to the file (relative to working directory or absolute)"),
3104
- startLine: z9.number().optional().describe("Start reading from this line (1-indexed)"),
3105
- endLine: z9.number().optional().describe("Stop reading at this line (1-indexed, inclusive)")
3673
+ inputSchema: z10.object({
3674
+ path: z10.string().describe("Path to the file (relative to working directory or absolute)"),
3675
+ startLine: z10.number().optional().describe("Start reading from this line (1-indexed)"),
3676
+ endLine: z10.number().optional().describe("Stop reading at this line (1-indexed, inclusive)")
3106
3677
  }),
3107
3678
  execute: async ({ path, startLine, endLine }) => {
3108
3679
  try {
3109
- const absolutePath = isAbsolute4(path) ? path : resolve8(workingDirectory, path);
3110
- if (!existsSync11(absolutePath)) {
3680
+ const absolutePath = isAbsolute5(path) ? path : resolve9(workingDirectory, path);
3681
+ if (!existsSync12(absolutePath)) {
3111
3682
  return {
3112
3683
  success: false,
3113
3684
  error: `File not found: ${path}`
@@ -3120,7 +3691,7 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
3120
3691
  error: `File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Use startLine/endLine to read portions.`
3121
3692
  };
3122
3693
  }
3123
- let content = await readFile7(absolutePath, "utf-8");
3694
+ let content = await readFile8(absolutePath, "utf-8");
3124
3695
  if (startLine !== void 0 || endLine !== void 0) {
3125
3696
  const lines = content.split("\n");
3126
3697
  const start = (startLine ?? 1) - 1;
@@ -3129,7 +3700,7 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
3129
3700
  }
3130
3701
  return {
3131
3702
  success: true,
3132
- path: relative7(workingDirectory, absolutePath),
3703
+ path: relative8(workingDirectory, absolutePath),
3133
3704
  content: truncateOutput(content, MAX_OUTPUT_CHARS4),
3134
3705
  lineCount: content.split("\n").length
3135
3706
  };
@@ -3141,17 +3712,17 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
3141
3712
  }
3142
3713
  }
3143
3714
  }),
3144
- list_dir: tool8({
3715
+ list_dir: tool9({
3145
3716
  description: "List contents of a directory. Shows files and subdirectories.",
3146
- inputSchema: z9.object({
3147
- path: z9.string().optional().default(".").describe("Directory path (relative to working directory)"),
3148
- recursive: z9.boolean().optional().default(false).describe("List recursively (be careful with large directories)"),
3149
- maxDepth: z9.number().optional().default(2).describe("Maximum depth for recursive listing")
3717
+ inputSchema: z10.object({
3718
+ path: z10.string().optional().default(".").describe("Directory path (relative to working directory)"),
3719
+ recursive: z10.boolean().optional().default(false).describe("List recursively (be careful with large directories)"),
3720
+ maxDepth: z10.number().optional().default(2).describe("Maximum depth for recursive listing")
3150
3721
  }),
3151
3722
  execute: async ({ path, recursive, maxDepth }) => {
3152
3723
  try {
3153
- const absolutePath = isAbsolute4(path) ? path : resolve8(workingDirectory, path);
3154
- if (!existsSync11(absolutePath)) {
3724
+ const absolutePath = isAbsolute5(path) ? path : resolve9(workingDirectory, path);
3725
+ if (!existsSync12(absolutePath)) {
3155
3726
  return {
3156
3727
  success: false,
3157
3728
  error: `Directory not found: ${path}`
@@ -3175,20 +3746,20 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
3175
3746
  const files = stdout.trim().split("\n").filter(Boolean);
3176
3747
  return {
3177
3748
  success: true,
3178
- path: relative7(workingDirectory, absolutePath) || ".",
3749
+ path: relative8(workingDirectory, absolutePath) || ".",
3179
3750
  files,
3180
3751
  count: files.length,
3181
3752
  recursive: true
3182
3753
  };
3183
3754
  } else {
3184
- const entries = await readdir3(absolutePath, { withFileTypes: true });
3755
+ const entries = await readdir4(absolutePath, { withFileTypes: true });
3185
3756
  const items = entries.slice(0, 200).map((e) => ({
3186
3757
  name: e.name,
3187
3758
  type: e.isDirectory() ? "directory" : "file"
3188
3759
  }));
3189
3760
  return {
3190
3761
  success: true,
3191
- path: relative7(workingDirectory, absolutePath) || ".",
3762
+ path: relative8(workingDirectory, absolutePath) || ".",
3192
3763
  items,
3193
3764
  count: items.length
3194
3765
  };
@@ -3200,6 +3771,9 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
3200
3771
  };
3201
3772
  }
3202
3773
  }
3774
+ }),
3775
+ code_graph: createCodeGraphTool({
3776
+ workingDirectory
3203
3777
  })
3204
3778
  };
3205
3779
  try {
@@ -3293,6 +3867,26 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
3293
3867
  context: m.symbolName || m.language
3294
3868
  });
3295
3869
  }
3870
+ } else if (step.toolName === "code_graph" && output.success) {
3871
+ matchCount += output.referenceCount || 0;
3872
+ if (output.filePath) {
3873
+ findings.push({
3874
+ type: "file",
3875
+ path: output.filePath,
3876
+ lineNumber: output.line,
3877
+ content: output.typeInfo ? truncateOutput(output.typeInfo, 300) : void 0,
3878
+ relevance: "high",
3879
+ context: `${output.kind || "symbol"}${output.referenceCount ? `, ${output.referenceCount} refs` : ""}`
3880
+ });
3881
+ }
3882
+ for (const page of (output.pages || []).slice(0, 10)) {
3883
+ findings.push({
3884
+ type: "file",
3885
+ path: page.path,
3886
+ relevance: "high",
3887
+ context: page.route ? `route: ${page.route}` : "page"
3888
+ });
3889
+ }
3296
3890
  }
3297
3891
  }
3298
3892
  }
@@ -3314,7 +3908,7 @@ function createSearchSubagent(model) {
3314
3908
  // src/tools/search.ts
3315
3909
  var MAX_RESULT_CHARS = 1e4;
3316
3910
  function createSearchTool(options) {
3317
- return tool9({
3911
+ return tool10({
3318
3912
  description: `Delegate an explore task to the explore_agent tool. Use this when you need to:
3319
3913
  - Find files or code matching a pattern
3320
3914
  - Explore the codebase structure
@@ -3324,11 +3918,12 @@ function createSearchTool(options) {
3324
3918
  The Explore agent will explore the codebase and return a summary of findings.
3325
3919
  This is more thorough than a simple grep because it can follow references and understand context.
3326
3920
  It also has access to semantic search to find code by meaning, not just text.
3921
+ It can also use code_graph to inspect a symbol's type hierarchy, references, and which pages/routes use it.
3327
3922
 
3328
3923
  CRITICAL: The explore agent has ZERO context. It cannot see the conversation, the user's message, devtools data, or any prior context. You MUST pass ALL relevant context via the "context" parameter. If the user selected a component (component name, file path, HTML, component stack) or there is a <devtools-context> block, you MUST copy that information into the "context" field verbatim. Without it the explore agent is searching blind.`,
3329
- inputSchema: z10.object({
3330
- query: z10.string().describe("What to search for. Be specific about what you're looking for."),
3331
- context: z10.string().describe("ALL context the explore agent needs. It has ZERO context on its own - no conversation history, no devtools data, nothing. You MUST include: any selected component info (name, file path, HTML, component stack), any <devtools-context> block (page URL, path, viewport), and any other relevant details from the user message. The explore agent literally only sees the query and this context field.")
3924
+ inputSchema: z11.object({
3925
+ query: z11.string().describe("What to search for. Be specific about what you're looking for."),
3926
+ context: z11.string().describe("ALL context the explore agent needs. It has ZERO context on its own - no conversation history, no devtools data, nothing. You MUST include: any selected component info (name, file path, HTML, component stack), any <devtools-context> block (page URL, path, viewport), and any other relevant details from the user message. The explore agent literally only sees the query and this context field.")
3332
3927
  }),
3333
3928
  execute: async ({ query, context }, toolOptions) => {
3334
3929
  const toolCallId = toolOptions.toolCallId || `explore_agent_${Date.now()}`;
@@ -3472,6 +4067,9 @@ async function createTools(options) {
3472
4067
  sessionId: options.sessionId,
3473
4068
  workingDirectory: options.workingDirectory,
3474
4069
  onProgress: options.onSearchProgress
4070
+ }),
4071
+ code_graph: createCodeGraphTool({
4072
+ workingDirectory: options.workingDirectory
3475
4073
  })
3476
4074
  };
3477
4075
  if (options.enableSemanticSearch !== false) {
@@ -3491,6 +4089,7 @@ async function createTools(options) {
3491
4089
  }
3492
4090
  export {
3493
4091
  createBashTool,
4092
+ createCodeGraphTool,
3494
4093
  createLinterTool,
3495
4094
  createLoadSkillTool,
3496
4095
  createReadFileTool,