wave-agent-sdk 0.8.3 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/agent.d.ts.map +1 -1
  2. package/dist/agent.js +7 -10
  3. package/dist/constants/tools.d.ts +1 -2
  4. package/dist/constants/tools.d.ts.map +1 -1
  5. package/dist/constants/tools.js +1 -2
  6. package/dist/managers/aiManager.d.ts +6 -10
  7. package/dist/managers/aiManager.d.ts.map +1 -1
  8. package/dist/managers/aiManager.js +38 -13
  9. package/dist/managers/hookManager.d.ts.map +1 -1
  10. package/dist/managers/hookManager.js +15 -1
  11. package/dist/managers/messageManager.d.ts +1 -0
  12. package/dist/managers/messageManager.d.ts.map +1 -1
  13. package/dist/managers/messageManager.js +8 -2
  14. package/dist/managers/permissionManager.d.ts.map +1 -1
  15. package/dist/managers/permissionManager.js +2 -7
  16. package/dist/managers/subagentManager.d.ts +5 -14
  17. package/dist/managers/subagentManager.d.ts.map +1 -1
  18. package/dist/managers/subagentManager.js +17 -65
  19. package/dist/managers/toolManager.d.ts +2 -2
  20. package/dist/managers/toolManager.d.ts.map +1 -1
  21. package/dist/managers/toolManager.js +4 -6
  22. package/dist/prompts/autoMemory.d.ts +2 -0
  23. package/dist/prompts/autoMemory.d.ts.map +1 -0
  24. package/dist/prompts/autoMemory.js +33 -0
  25. package/dist/prompts/index.d.ts +4 -0
  26. package/dist/prompts/index.d.ts.map +1 -1
  27. package/dist/prompts/index.js +10 -3
  28. package/dist/services/aiService.js +6 -7
  29. package/dist/services/configurationService.d.ts +28 -16
  30. package/dist/services/configurationService.d.ts.map +1 -1
  31. package/dist/services/configurationService.js +87 -28
  32. package/dist/services/hook.js +2 -2
  33. package/dist/services/initializationService.d.ts.map +1 -1
  34. package/dist/services/initializationService.js +20 -9
  35. package/dist/services/memory.d.ts +22 -4
  36. package/dist/services/memory.d.ts.map +1 -1
  37. package/dist/services/memory.js +132 -73
  38. package/dist/tools/agentTool.d.ts +6 -0
  39. package/dist/tools/agentTool.d.ts.map +1 -0
  40. package/dist/tools/{taskTool.js → agentTool.js} +42 -30
  41. package/dist/tools/grepTool.js +2 -2
  42. package/dist/tools/skillTool.js +2 -2
  43. package/dist/tools/types.d.ts +1 -1
  44. package/dist/tools/types.d.ts.map +1 -1
  45. package/dist/types/configuration.d.ts +2 -0
  46. package/dist/types/configuration.d.ts.map +1 -1
  47. package/dist/types/hooks.d.ts +2 -0
  48. package/dist/types/hooks.d.ts.map +1 -1
  49. package/dist/types/hooks.js +17 -7
  50. package/dist/utils/builtinSubagents.d.ts.map +1 -1
  51. package/dist/utils/builtinSubagents.js +1 -3
  52. package/dist/utils/containerSetup.d.ts +0 -5
  53. package/dist/utils/containerSetup.d.ts.map +1 -1
  54. package/dist/utils/containerSetup.js +8 -11
  55. package/dist/utils/fileSearch.d.ts +1 -6
  56. package/dist/utils/fileSearch.d.ts.map +1 -1
  57. package/dist/utils/fileSearch.js +104 -75
  58. package/dist/utils/gitUtils.d.ts +6 -0
  59. package/dist/utils/gitUtils.d.ts.map +1 -1
  60. package/dist/utils/gitUtils.js +65 -1
  61. package/dist/utils/path.d.ts +0 -10
  62. package/dist/utils/path.d.ts.map +1 -1
  63. package/dist/utils/path.js +0 -61
  64. package/package.json +2 -1
  65. package/src/agent.ts +9 -20
  66. package/src/constants/tools.ts +1 -2
  67. package/src/managers/aiManager.ts +73 -28
  68. package/src/managers/hookManager.ts +22 -1
  69. package/src/managers/messageManager.ts +12 -2
  70. package/src/managers/permissionManager.ts +1 -7
  71. package/src/managers/subagentManager.ts +20 -81
  72. package/src/managers/toolManager.ts +4 -6
  73. package/src/prompts/autoMemory.ts +33 -0
  74. package/src/prompts/index.ts +15 -2
  75. package/src/services/aiService.ts +8 -8
  76. package/src/services/configurationService.ts +100 -28
  77. package/src/services/hook.ts +2 -2
  78. package/src/services/initializationService.ts +24 -12
  79. package/src/services/memory.ts +144 -82
  80. package/src/tools/{taskTool.ts → agentTool.ts} +42 -30
  81. package/src/tools/grepTool.ts +2 -2
  82. package/src/tools/skillTool.ts +2 -2
  83. package/src/tools/types.ts +1 -1
  84. package/src/types/configuration.ts +2 -0
  85. package/src/types/hooks.ts +25 -9
  86. package/src/utils/builtinSubagents.ts +0 -3
  87. package/src/utils/containerSetup.ts +8 -21
  88. package/src/utils/fileSearch.ts +112 -80
  89. package/src/utils/gitUtils.ts +66 -1
  90. package/src/utils/path.ts +0 -62
  91. package/dist/tools/lsTool.d.ts +0 -6
  92. package/dist/tools/lsTool.d.ts.map +0 -1
  93. package/dist/tools/lsTool.js +0 -175
  94. package/dist/tools/taskTool.d.ts +0 -6
  95. package/dist/tools/taskTool.d.ts.map +0 -1
  96. package/src/tools/lsTool.ts +0 -212
@@ -1,6 +1,6 @@
1
1
  import type { ToolPlugin, ToolResult, ToolContext } from "./types.js";
2
2
  import { EXPLORE_SUBAGENT_TYPE } from "../constants/subagents.js";
3
- import { TASK_TOOL_NAME } from "../constants/tools.js";
3
+ import { AGENT_TOOL_NAME } from "../constants/tools.js";
4
4
  import type { SubagentConfiguration } from "../utils/subagentParser.js";
5
5
  import {
6
6
  countToolBlocks,
@@ -8,16 +8,16 @@ import {
8
8
  } from "../utils/messageOperations.js";
9
9
 
10
10
  /**
11
- * Task tool plugin for delegating tasks to specialized subagents
11
+ * Agent tool plugin for launching specialized agents to handle complex tasks
12
12
  */
13
- export const taskTool: ToolPlugin = {
14
- name: TASK_TOOL_NAME,
13
+ export const agentTool: ToolPlugin = {
14
+ name: AGENT_TOOL_NAME,
15
15
  config: {
16
16
  type: "function" as const,
17
17
  function: {
18
- name: TASK_TOOL_NAME,
18
+ name: AGENT_TOOL_NAME,
19
19
  description:
20
- "Delegate a task to a specialized subagent. Use this when you need specialized expertise or want to break down complex work into focused subtasks.",
20
+ "Launch a new agent to handle complex, multi-step tasks autonomously.",
21
21
  parameters: {
22
22
  type: "object",
23
23
  properties: {
@@ -47,19 +47,31 @@ export const taskTool: ToolPlugin = {
47
47
  prompt: (args?: { availableSubagents?: SubagentConfiguration[] }) => {
48
48
  const subagentList = args?.availableSubagents
49
49
  ? args.availableSubagents
50
- .map((config) => `- ${config.name}: ${config.description}`)
50
+ .map((config) => {
51
+ let toolsStr = "";
52
+ if (config.tools && config.tools.length > 0) {
53
+ toolsStr = ` (Tools: ${config.tools.join(", ")})`;
54
+ } else {
55
+ toolsStr = " (Tools: *)";
56
+ }
57
+
58
+ return `- ${config.name}: ${config.description}${toolsStr}`;
59
+ })
51
60
  .join("\n")
52
61
  : "";
53
62
 
54
- return `
55
- Delegate a task to a specialized subagent. Use this when you need specialized expertise or want to break down complex work into focused subtasks.
63
+ return `Launch a new agent to handle complex, multi-step tasks autonomously.
64
+
65
+ The Agent tool launches specialized agents (subprocesses) that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.
66
+
67
+ Available agent types and the tools they have access to:
68
+ ${subagentList || "No agents configured"}
56
69
 
57
- Available subagents:
58
- ${subagentList || "No subagents configured"}
70
+ When using the Agent tool, you must specify a subagent_type parameter to select which agent type to use.
59
71
 
60
- - When doing file search, prefer to use the ${TASK_TOOL_NAME} tool in order to reduce context usage.
61
- - You should proactively use the ${TASK_TOOL_NAME} tool with specialized agents when the task at hand matches the agent's description.
62
- - VERY IMPORTANT: When exploring the codebase to gather context or to answer a question that is not a needle query for a specific file/class/function, it is CRITICAL that you use the ${TASK_TOOL_NAME} tool with subagent_type=${EXPLORE_SUBAGENT_TYPE} instead of running search commands directly.`;
72
+ - When doing file search, prefer to use the ${AGENT_TOOL_NAME} tool in order to reduce context usage.
73
+ - You should proactively use the ${AGENT_TOOL_NAME} tool with specialized agents when the task at hand matches the agent's description.
74
+ - VERY IMPORTANT: When exploring the codebase to gather context or to answer a question that is not a needle query for a specific file/class/function, it is CRITICAL that you use the ${AGENT_TOOL_NAME} tool with subagent_type=${EXPLORE_SUBAGENT_TYPE} instead of running search commands directly.`;
63
75
  },
64
76
 
65
77
  execute: async (
@@ -72,7 +84,7 @@ ${subagentList || "No subagents configured"}
72
84
  success: false,
73
85
  content: "",
74
86
  error: "Subagent manager not available in tool context",
75
- shortResult: "Task delegation failed",
87
+ shortResult: "Agent delegation failed",
76
88
  };
77
89
  }
78
90
 
@@ -87,7 +99,7 @@ ${subagentList || "No subagents configured"}
87
99
  success: false,
88
100
  content: "",
89
101
  error: "description parameter is required and must be a string",
90
- shortResult: "Task delegation failed",
102
+ shortResult: "Agent delegation failed",
91
103
  };
92
104
  }
93
105
 
@@ -96,7 +108,7 @@ ${subagentList || "No subagents configured"}
96
108
  success: false,
97
109
  content: "",
98
110
  error: "prompt parameter is required and must be a string",
99
- shortResult: "Task delegation failed",
111
+ shortResult: "Agent delegation failed",
100
112
  };
101
113
  }
102
114
 
@@ -105,7 +117,7 @@ ${subagentList || "No subagents configured"}
105
117
  success: false,
106
118
  content: "",
107
119
  error: "subagent_type parameter is required and must be a string",
108
- shortResult: "Task delegation failed",
120
+ shortResult: "Agent delegation failed",
109
121
  };
110
122
  }
111
123
 
@@ -121,14 +133,14 @@ ${subagentList || "No subagents configured"}
121
133
  return {
122
134
  success: false,
123
135
  content: "",
124
- error: `No subagent found matching "${subagent_type}". Available subagents: ${availableNames || "none"}`,
125
- shortResult: "Subagent not found",
136
+ error: `No agent found matching "${subagent_type}". Available agents: ${availableNames || "none"}`,
137
+ shortResult: "Agent not found",
126
138
  };
127
139
  }
128
140
 
129
141
  let isBackgrounded = false;
130
142
 
131
- // Create subagent instance and execute task
143
+ // Create subagent instance and execute agent
132
144
  const instance = await subagentManager.createInstance(
133
145
  configuration,
134
146
  {
@@ -173,8 +185,8 @@ ${subagentList || "No subagents configured"}
173
185
  await subagentManager.backgroundInstance(instance.subagentId);
174
186
  resolve({
175
187
  success: true,
176
- content: "Task backgrounded",
177
- shortResult: "Task backgrounded",
188
+ content: "Agent backgrounded",
189
+ shortResult: "Agent backgrounded",
178
190
  isManuallyBackgrounded: true,
179
191
  });
180
192
  },
@@ -182,7 +194,7 @@ ${subagentList || "No subagents configured"}
182
194
  }
183
195
 
184
196
  try {
185
- const result = await subagentManager.executeTask(
197
+ const result = await subagentManager.executeAgent(
186
198
  instance,
187
199
  prompt,
188
200
  context.abortSignal,
@@ -196,13 +208,13 @@ ${subagentList || "No subagents configured"}
196
208
  if (run_in_background) {
197
209
  resolve({
198
210
  success: true,
199
- content: `Task started in background with ID: ${result}`,
200
- shortResult: `Task started in background: ${result}`,
211
+ content: `Agent started in background with ID: ${result}`,
212
+ shortResult: `Agent started in background: ${result}`,
201
213
  });
202
214
  return;
203
215
  }
204
216
 
205
- // Cleanup subagent instance after task completion
217
+ // Cleanup subagent instance after agent completion
206
218
  subagentManager.cleanupInstance(instance.subagentId);
207
219
 
208
220
  const messages = instance.messageManager.getMessages();
@@ -213,14 +225,14 @@ ${subagentList || "No subagents configured"}
213
225
  resolve({
214
226
  success: true,
215
227
  content: result,
216
- shortResult: `Task completed${summary ? ` ${summary}` : ""}`,
228
+ shortResult: `Agent completed${summary ? ` ${summary}` : ""}`,
217
229
  });
218
230
  } catch (error) {
219
231
  if (!isBackgrounded) {
220
232
  resolve({
221
233
  success: false,
222
234
  content: "",
223
- error: `Task delegation failed: ${error instanceof Error ? error.message : String(error)}`,
235
+ error: `Agent delegation failed: ${error instanceof Error ? error.message : String(error)}`,
224
236
  shortResult: "Delegation error",
225
237
  });
226
238
  }
@@ -237,7 +249,7 @@ ${subagentList || "No subagents configured"}
237
249
  return {
238
250
  success: false,
239
251
  content: "",
240
- error: `Task delegation failed: ${error instanceof Error ? error.message : String(error)}`,
252
+ error: `Agent delegation failed: ${error instanceof Error ? error.message : String(error)}`,
241
253
  shortResult: "Delegation error",
242
254
  };
243
255
  }
@@ -6,7 +6,7 @@ import { getDisplayPath } from "../utils/path.js";
6
6
  import {
7
7
  GREP_TOOL_NAME,
8
8
  BASH_TOOL_NAME,
9
- TASK_TOOL_NAME,
9
+ AGENT_TOOL_NAME,
10
10
  } from "../constants/tools.js";
11
11
 
12
12
  /**
@@ -94,7 +94,7 @@ export const grepTool: ToolPlugin = {
94
94
  - Supports full regex syntax (e.g., "log.*Error", "function\\s+\\w+")
95
95
  - Filter files with glob parameter (e.g., "*.js", "**/*.tsx") or type parameter (e.g., "js", "py", "rust")
96
96
  - Output modes: "content" shows matching lines, "files_with_matches" shows only file paths (default), "count" shows match counts
97
- - Use ${TASK_TOOL_NAME} tool for open-ended searches requiring multiple rounds
97
+ - Use ${AGENT_TOOL_NAME} tool for open-ended searches requiring multiple rounds
98
98
  - Pattern syntax: Uses ripgrep (not grep) - literal braces need escaping (use \`interface\\{\\}\` to find \`interface{}\` in Go code)
99
99
  - Multiline matching: By default patterns match within single lines only. For cross-line patterns like \`struct \\{[\\s\\S]*?field\`, use \`multiline: true\`
100
100
  `,
@@ -155,14 +155,14 @@ export const skillTool: ToolPlugin = {
155
155
  );
156
156
 
157
157
  try {
158
- const result = await subagentManager.executeTask(
158
+ const result = await subagentManager.executeAgent(
159
159
  instance,
160
160
  skillResult.content,
161
161
  context.abortSignal,
162
162
  false,
163
163
  );
164
164
 
165
- // Cleanup subagent instance after task completion
165
+ // Cleanup subagent instance after agent completion
166
166
  subagentManager.cleanupInstance(instance.subagentId);
167
167
 
168
168
  const messages = instance.messageManager.getMessages();
@@ -73,7 +73,7 @@ export interface ToolContext {
73
73
  foregroundTaskManager?: import("../types/processes.js").IForegroundTaskManager;
74
74
  /** Task manager instance for task management */
75
75
  taskManager: import("../services/taskManager.js").TaskManager;
76
- /** Subagent manager instance for task delegation */
76
+ /** Subagent manager instance for agent delegation */
77
77
  subagentManager?: import("../managers/subagentManager.js").SubagentManager;
78
78
  /** Skill manager instance for skill invocation */
79
79
  skillManager?: import("../managers/skillManager.js").SkillManager;
@@ -32,6 +32,8 @@ export interface WaveConfiguration {
32
32
  enabledPlugins?: Record<string, boolean>;
33
33
  /** Preferred language for agent communication */
34
34
  language?: string;
35
+ /** Whether auto-memory is enabled */
36
+ autoMemoryEnabled?: boolean;
35
37
  }
36
38
 
37
39
  /**
@@ -26,6 +26,8 @@ export type HookEvent =
26
26
  export interface HookCommand {
27
27
  type: "command";
28
28
  command: string;
29
+ async?: boolean;
30
+ timeout?: number; // seconds
29
31
  }
30
32
 
31
33
  // Hook event configuration with optional pattern matching
@@ -104,15 +106,29 @@ export function isValidHookEvent(event: string): event is HookEvent {
104
106
  }
105
107
 
106
108
  export function isValidHookCommand(cmd: unknown): cmd is HookCommand {
107
- return (
108
- typeof cmd === "object" &&
109
- cmd !== null &&
110
- "type" in cmd &&
111
- cmd.type === "command" &&
112
- "command" in cmd &&
113
- typeof cmd.command === "string" &&
114
- cmd.command.length > 0
115
- );
109
+ if (
110
+ typeof cmd !== "object" ||
111
+ cmd === null ||
112
+ !("type" in cmd) ||
113
+ cmd.type !== "command" ||
114
+ !("command" in cmd) ||
115
+ typeof cmd.command !== "string" ||
116
+ cmd.command.length === 0
117
+ ) {
118
+ return false;
119
+ }
120
+
121
+ const hookCmd = cmd as Record<string, unknown>;
122
+
123
+ if ("async" in hookCmd && typeof hookCmd.async !== "boolean") {
124
+ return false;
125
+ }
126
+
127
+ if ("timeout" in hookCmd && typeof hookCmd.timeout !== "number") {
128
+ return false;
129
+ }
130
+
131
+ return true;
116
132
  }
117
133
 
118
134
  export function isValidHookEventConfig(
@@ -15,7 +15,6 @@ import {
15
15
  GLOB_TOOL_NAME,
16
16
  GREP_TOOL_NAME,
17
17
  READ_TOOL_NAME,
18
- LS_TOOL_NAME,
19
18
  LSP_TOOL_NAME,
20
19
  } from "../constants/tools.js";
21
20
  import type { SubagentConfiguration } from "./subagentParser.js";
@@ -79,7 +78,6 @@ function createExploreSubagent(): SubagentConfiguration {
79
78
  GREP_TOOL_NAME,
80
79
  READ_TOOL_NAME,
81
80
  BASH_TOOL_NAME,
82
- LS_TOOL_NAME,
83
81
  LSP_TOOL_NAME,
84
82
  ];
85
83
 
@@ -107,7 +105,6 @@ function createPlanSubagent(): SubagentConfiguration {
107
105
  GREP_TOOL_NAME,
108
106
  READ_TOOL_NAME,
109
107
  BASH_TOOL_NAME,
110
- LS_TOOL_NAME,
111
108
  LSP_TOOL_NAME,
112
109
  ];
113
110
 
@@ -21,6 +21,7 @@ import { SubagentManager } from "../managers/subagentManager.js";
21
21
  import { LiveConfigManager } from "../managers/liveConfigManager.js";
22
22
  import { ConfigurationService } from "../services/configurationService.js";
23
23
  import { ReversionService } from "../services/reversionService.js";
24
+ import { MemoryService } from "../services/memory.js";
24
25
  import type { AgentOptions } from "../types/index.js";
25
26
  import type {
26
27
  PermissionMode,
@@ -29,7 +30,6 @@ import type {
29
30
  BackgroundTask,
30
31
  ToolPermissionContext,
31
32
  } from "../types/index.js";
32
- import type { GatewayConfig, ModelConfig } from "../types/config.js";
33
33
 
34
34
  import { logger } from "./globalLogger.js";
35
35
 
@@ -48,12 +48,6 @@ export interface AgentContainerSetupOptions {
48
48
  setPermissionMode: (mode: PermissionMode) => void;
49
49
  addPermissionRule: (rule: string) => Promise<void>;
50
50
  addUsage: (usage: Usage) => void;
51
-
52
- // Getters
53
- getGatewayConfig: () => GatewayConfig;
54
- getModelConfig: () => ModelConfig;
55
- getMaxInputTokens: () => number;
56
- getLanguage: () => string | undefined;
57
51
  }
58
52
 
59
53
  export function setupAgentContainer(
@@ -72,10 +66,6 @@ export function setupAgentContainer(
72
66
  setPermissionMode,
73
67
  addPermissionRule,
74
68
  addUsage,
75
- getGatewayConfig,
76
- getModelConfig,
77
- getMaxInputTokens,
78
- getLanguage,
79
69
  } = setupOptions;
80
70
 
81
71
  const callbacks = options.callbacks || {};
@@ -85,6 +75,9 @@ export function setupAgentContainer(
85
75
  container.register("ForegroundTaskManager", foregroundTaskManager);
86
76
  container.register("ConfigurationService", configurationService);
87
77
 
78
+ const memoryService = new MemoryService(container);
79
+ container.register("MemoryService", memoryService);
80
+
88
81
  const memoryRuleManager = new MemoryRuleManager(container, { workdir });
89
82
  container.register("MemoryRuleManager", memoryRuleManager);
90
83
 
@@ -139,6 +132,10 @@ export function setupAgentContainer(
139
132
  container.register("LspManager", lspManager);
140
133
 
141
134
  const permissionManager = new PermissionManager(container, { workdir });
135
+ if (configurationService.resolveAutoMemoryEnabled()) {
136
+ const autoMemoryDir = memoryService.getAutoMemoryDirectory(workdir);
137
+ permissionManager.updateAdditionalDirectories([autoMemoryDir]);
138
+ }
142
139
  container.register("PermissionManager", permissionManager);
143
140
  permissionManager.setOnConfiguredDefaultModeChange((mode) => {
144
141
  handlePlanModeTransition(mode);
@@ -246,11 +243,6 @@ export function setupAgentContainer(
246
243
  onSubagentLatestTotalTokensChange:
247
244
  callbacks.onSubagentLatestTotalTokensChange,
248
245
  },
249
- getGatewayConfig,
250
- getModelConfig,
251
- getMaxInputTokens,
252
- getLanguage,
253
- getEnvironmentVars: () => configurationService.getEnvironmentVars(),
254
246
  onUsageAdded: (usage: Usage) => addUsage(usage),
255
247
  });
256
248
  container.register("SubagentManager", subagentManager);
@@ -263,11 +255,6 @@ export function setupAgentContainer(
263
255
  workdir,
264
256
  systemPrompt,
265
257
  stream,
266
- getGatewayConfig,
267
- getModelConfig,
268
- getMaxInputTokens,
269
- getLanguage,
270
- getEnvironmentVars: () => configurationService.getEnvironmentVars(),
271
258
  });
272
259
  container.register("AIManager", aiManager);
273
260
 
@@ -1,26 +1,78 @@
1
- import { globIterate, type Path } from "glob";
1
+ import { spawn } from "child_process";
2
+ import { rgPath } from "@vscode/ripgrep";
3
+ import fuzzysort from "fuzzysort";
2
4
  import { getGlobIgnorePatterns } from "./fileFilter.js";
3
5
  import type { FileItem } from "../types/fileSearch.js";
6
+ import { logger } from "./globalLogger.js";
4
7
 
5
8
  /**
6
- * Convert Path objects to FileItem objects
9
+ * Execute ripgrep to get all file paths
7
10
  */
8
- export const convertPathsToFileItems = (paths: Path[]): FileItem[] => {
9
- return paths.map((pathObj) => {
10
- const isDirectory = pathObj.isDirectory();
11
- let path = pathObj.relative();
12
- if (isDirectory && !path.endsWith("/")) {
13
- path += "/";
14
- }
15
- return {
16
- path,
17
- type: isDirectory ? "directory" : "file",
18
- };
11
+ async function getAllFiles(workingDirectory: string): Promise<string[]> {
12
+ if (!rgPath) {
13
+ throw new Error("ripgrep is not available");
14
+ }
15
+
16
+ const ignorePatterns = getGlobIgnorePatterns(workingDirectory);
17
+ const rgArgs = ["--files", "--color=never", "--hidden"];
18
+ for (const pattern of ignorePatterns) {
19
+ rgArgs.push("--glob", `!${pattern}`);
20
+ }
21
+
22
+ return new Promise((resolve, reject) => {
23
+ const child = spawn(rgPath, rgArgs, {
24
+ cwd: workingDirectory,
25
+ stdio: ["ignore", "pipe", "pipe"],
26
+ });
27
+
28
+ let stdout = "";
29
+ let stderr = "";
30
+
31
+ child.stdout?.on("data", (data) => {
32
+ stdout += data.toString();
33
+ });
34
+
35
+ child.stderr?.on("data", (data) => {
36
+ stderr += data.toString();
37
+ });
38
+
39
+ child.on("close", (code) => {
40
+ if (code !== 0 && code !== 1) {
41
+ reject(new Error(`ripgrep failed: ${stderr}`));
42
+ return;
43
+ }
44
+ const files = stdout
45
+ .trim()
46
+ .split("\n")
47
+ .filter((f) => f.length > 0)
48
+ .map((f) => f.replace(/\\/g, "/")); // Normalize to forward slashes
49
+ resolve(files);
50
+ });
51
+
52
+ child.on("error", (err) => {
53
+ reject(err);
54
+ });
19
55
  });
20
- };
56
+ }
57
+
58
+ /**
59
+ * Derive directory paths from file paths
60
+ */
61
+ function deriveDirectories(files: string[]): string[] {
62
+ const dirs = new Set<string>();
63
+ for (const file of files) {
64
+ const parts = file.split("/");
65
+ // Add all parent directories
66
+ for (let i = 1; i < parts.length; i++) {
67
+ const dir = parts.slice(0, i).join("/") + "/";
68
+ dirs.add(dir);
69
+ }
70
+ }
71
+ return Array.from(dirs);
72
+ }
21
73
 
22
74
  /**
23
- * Search files and directories using glob patterns
75
+ * Search files and directories using fuzzy matching
24
76
  */
25
77
  export const searchFiles = async (
26
78
  query: string,
@@ -32,83 +84,63 @@ export const searchFiles = async (
32
84
  const { maxResults = 10, workingDirectory = process.cwd() } = options || {};
33
85
 
34
86
  try {
35
- const globOptions: import("glob").GlobOptionsWithFileTypesTrue = {
36
- ignore: getGlobIgnorePatterns(workingDirectory),
37
- maxDepth: 10,
38
- nocase: true, // Case insensitive
39
- dot: true, // Include hidden files and directories
40
- cwd: workingDirectory, // Specify search root directory
41
- withFileTypes: true, // Get Path objects instead of strings
42
- };
43
-
44
- // Build glob patterns based on query
45
- let patterns: string[] = [];
87
+ const files = await getAllFiles(workingDirectory);
46
88
 
47
89
  if (!query.trim()) {
48
- // When query is empty, show some common file types and directories
49
- patterns = [
50
- "**/*.{ts,tsx,js,jsx,json,py,java}", // Combine common file extensions
51
- "*/", // First level directories
52
- ];
53
- } else {
54
- // Build multiple glob patterns to support more flexible search
55
- patterns = [
56
- // Match files with filenames containing query
57
- `**/*${query}*`,
58
- // Match files with query in path (match directory names)
59
- `**/${query}*/**/*`,
60
- // Match directory names containing query
61
- `**/*${query}*/`,
62
- // Match directories containing query in path
63
- `**/${query}*/`,
64
- ];
65
- }
90
+ // When query is empty, show some common file types and top-level directories
91
+ const commonExtensions = new Set([
92
+ "ts",
93
+ "tsx",
94
+ "js",
95
+ "jsx",
96
+ "json",
97
+ "py",
98
+ "java",
99
+ ]);
100
+ const results: FileItem[] = [];
101
+ const seenDirs = new Set<string>();
66
102
 
67
- // Collect results until we reach maxResults
68
- const collectedPaths: Path[] = [];
69
- const seenPaths = new Set<string>();
70
-
71
- // Process each pattern sequentially
72
- for (const pattern of patterns) {
73
- if (collectedPaths.length >= maxResults) {
74
- break;
75
- }
76
-
77
- // Use globIterate to get results one by one
78
- const iterator = globIterate(pattern, globOptions) as AsyncGenerator<
79
- Path,
80
- void,
81
- void
82
- >;
83
-
84
- for await (const pathObj of iterator) {
85
- if (collectedPaths.length >= maxResults) {
86
- // Stop the iterator when we have enough results
87
- break;
103
+ for (const file of files) {
104
+ const parts = file.split("/");
105
+ if (parts.length > 1) {
106
+ const topDir = parts[0] + "/";
107
+ if (!seenDirs.has(topDir)) {
108
+ seenDirs.add(topDir);
109
+ results.push({ path: topDir, type: "directory" });
110
+ }
88
111
  }
89
112
 
90
- const relativePath = pathObj.relative();
91
- if (!seenPaths.has(relativePath)) {
92
- seenPaths.add(relativePath);
93
- collectedPaths.push(pathObj);
113
+ const ext = file.split(".").pop();
114
+ if (ext && commonExtensions.has(ext)) {
115
+ results.push({ path: file, type: "file" });
94
116
  }
95
117
  }
118
+
119
+ // Sort: directories first, then files, then alphabetically
120
+ return results
121
+ .sort((a, b) => {
122
+ if (a.type === "directory" && b.type === "file") return -1;
123
+ if (a.type === "file" && b.type === "directory") return 1;
124
+ return a.path.localeCompare(b.path);
125
+ })
126
+ .slice(0, maxResults);
96
127
  }
97
128
 
98
- // Sort collected paths: directories first, then files
99
- collectedPaths.sort((a, b) => {
100
- const aIsDir = a.isDirectory();
101
- const bIsDir = b.isDirectory();
102
- if (aIsDir && !bIsDir) return -1;
103
- if (!aIsDir && bIsDir) return 1;
104
- return a.relative().localeCompare(b.relative());
129
+ const directories = deriveDirectories(files);
130
+ const allItems: FileItem[] = [
131
+ ...files.map((f) => ({ path: f, type: "file" as const })),
132
+ ...directories.map((d) => ({ path: d, type: "directory" as const })),
133
+ ];
134
+
135
+ const fuzzyResults = fuzzysort.go(query, allItems, {
136
+ key: "path",
137
+ limit: maxResults,
138
+ threshold: 0,
105
139
  });
106
140
 
107
- // Convert to FileItems
108
- const fileItems = convertPathsToFileItems(collectedPaths);
109
- return fileItems;
141
+ return fuzzyResults.map((res) => res.obj);
110
142
  } catch (error) {
111
- console.error("Glob search error:", error);
143
+ logger.error("Fuzzy search error:", error);
112
144
  return [];
113
145
  }
114
146
  };