wave-agent-sdk 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/README.md +32 -0
  2. package/dist/agent.d.ts +96 -0
  3. package/dist/agent.d.ts.map +1 -0
  4. package/dist/agent.js +286 -0
  5. package/dist/hooks/executor.d.ts +56 -0
  6. package/dist/hooks/executor.d.ts.map +1 -0
  7. package/dist/hooks/executor.js +312 -0
  8. package/dist/hooks/index.d.ts +17 -0
  9. package/dist/hooks/index.d.ts.map +1 -0
  10. package/dist/hooks/index.js +14 -0
  11. package/dist/hooks/manager.d.ts +90 -0
  12. package/dist/hooks/manager.d.ts.map +1 -0
  13. package/dist/hooks/manager.js +395 -0
  14. package/dist/hooks/matcher.d.ts +49 -0
  15. package/dist/hooks/matcher.d.ts.map +1 -0
  16. package/dist/hooks/matcher.js +147 -0
  17. package/dist/hooks/settings.d.ts +46 -0
  18. package/dist/hooks/settings.d.ts.map +1 -0
  19. package/dist/hooks/settings.js +100 -0
  20. package/dist/hooks/types.d.ts +80 -0
  21. package/dist/hooks/types.d.ts.map +1 -0
  22. package/dist/hooks/types.js +59 -0
  23. package/dist/index.d.ts +16 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +20 -0
  26. package/dist/managers/aiManager.d.ts +61 -0
  27. package/dist/managers/aiManager.d.ts.map +1 -0
  28. package/dist/managers/aiManager.js +415 -0
  29. package/dist/managers/backgroundBashManager.d.ts +27 -0
  30. package/dist/managers/backgroundBashManager.d.ts.map +1 -0
  31. package/dist/managers/backgroundBashManager.js +166 -0
  32. package/dist/managers/bashManager.d.ts +20 -0
  33. package/dist/managers/bashManager.d.ts.map +1 -0
  34. package/dist/managers/bashManager.js +66 -0
  35. package/dist/managers/mcpManager.d.ts +63 -0
  36. package/dist/managers/mcpManager.d.ts.map +1 -0
  37. package/dist/managers/mcpManager.js +378 -0
  38. package/dist/managers/messageManager.d.ts +85 -0
  39. package/dist/managers/messageManager.d.ts.map +1 -0
  40. package/dist/managers/messageManager.js +265 -0
  41. package/dist/managers/skillManager.d.ts +59 -0
  42. package/dist/managers/skillManager.d.ts.map +1 -0
  43. package/dist/managers/skillManager.js +317 -0
  44. package/dist/managers/slashCommandManager.d.ts +77 -0
  45. package/dist/managers/slashCommandManager.d.ts.map +1 -0
  46. package/dist/managers/slashCommandManager.js +208 -0
  47. package/dist/managers/toolManager.d.ts +23 -0
  48. package/dist/managers/toolManager.d.ts.map +1 -0
  49. package/dist/managers/toolManager.js +79 -0
  50. package/dist/services/aiService.d.ts +28 -0
  51. package/dist/services/aiService.d.ts.map +1 -0
  52. package/dist/services/aiService.js +180 -0
  53. package/dist/services/memory.d.ts +8 -0
  54. package/dist/services/memory.d.ts.map +1 -0
  55. package/dist/services/memory.js +128 -0
  56. package/dist/services/session.d.ts +54 -0
  57. package/dist/services/session.d.ts.map +1 -0
  58. package/dist/services/session.js +196 -0
  59. package/dist/tools/bashTool.d.ts +14 -0
  60. package/dist/tools/bashTool.d.ts.map +1 -0
  61. package/dist/tools/bashTool.js +351 -0
  62. package/dist/tools/deleteFileTool.d.ts +6 -0
  63. package/dist/tools/deleteFileTool.d.ts.map +1 -0
  64. package/dist/tools/deleteFileTool.js +67 -0
  65. package/dist/tools/editTool.d.ts +6 -0
  66. package/dist/tools/editTool.d.ts.map +1 -0
  67. package/dist/tools/editTool.js +168 -0
  68. package/dist/tools/globTool.d.ts +6 -0
  69. package/dist/tools/globTool.d.ts.map +1 -0
  70. package/dist/tools/globTool.js +113 -0
  71. package/dist/tools/grepTool.d.ts +6 -0
  72. package/dist/tools/grepTool.d.ts.map +1 -0
  73. package/dist/tools/grepTool.js +268 -0
  74. package/dist/tools/lsTool.d.ts +6 -0
  75. package/dist/tools/lsTool.d.ts.map +1 -0
  76. package/dist/tools/lsTool.js +160 -0
  77. package/dist/tools/multiEditTool.d.ts +6 -0
  78. package/dist/tools/multiEditTool.d.ts.map +1 -0
  79. package/dist/tools/multiEditTool.js +222 -0
  80. package/dist/tools/readTool.d.ts +6 -0
  81. package/dist/tools/readTool.d.ts.map +1 -0
  82. package/dist/tools/readTool.js +136 -0
  83. package/dist/tools/types.d.ts +35 -0
  84. package/dist/tools/types.d.ts.map +1 -0
  85. package/dist/tools/types.js +4 -0
  86. package/dist/tools/writeTool.d.ts +6 -0
  87. package/dist/tools/writeTool.d.ts.map +1 -0
  88. package/dist/tools/writeTool.js +138 -0
  89. package/dist/types.d.ts +212 -0
  90. package/dist/types.d.ts.map +1 -0
  91. package/dist/types.js +13 -0
  92. package/dist/utils/bashHistory.d.ts +46 -0
  93. package/dist/utils/bashHistory.d.ts.map +1 -0
  94. package/dist/utils/bashHistory.js +236 -0
  95. package/dist/utils/commandArgumentParser.d.ts +34 -0
  96. package/dist/utils/commandArgumentParser.d.ts.map +1 -0
  97. package/dist/utils/commandArgumentParser.js +123 -0
  98. package/dist/utils/constants.d.ts +27 -0
  99. package/dist/utils/constants.d.ts.map +1 -0
  100. package/dist/utils/constants.js +28 -0
  101. package/dist/utils/convertMessagesForAPI.d.ts +9 -0
  102. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -0
  103. package/dist/utils/convertMessagesForAPI.js +189 -0
  104. package/dist/utils/customCommands.d.ts +14 -0
  105. package/dist/utils/customCommands.d.ts.map +1 -0
  106. package/dist/utils/customCommands.js +71 -0
  107. package/dist/utils/fileFilter.d.ts +26 -0
  108. package/dist/utils/fileFilter.d.ts.map +1 -0
  109. package/dist/utils/fileFilter.js +177 -0
  110. package/dist/utils/markdownParser.d.ts +27 -0
  111. package/dist/utils/markdownParser.d.ts.map +1 -0
  112. package/dist/utils/markdownParser.js +109 -0
  113. package/dist/utils/mcpUtils.d.ts +24 -0
  114. package/dist/utils/mcpUtils.d.ts.map +1 -0
  115. package/dist/utils/mcpUtils.js +51 -0
  116. package/dist/utils/messageOperations.d.ts +118 -0
  117. package/dist/utils/messageOperations.d.ts.map +1 -0
  118. package/dist/utils/messageOperations.js +334 -0
  119. package/dist/utils/path.d.ts +25 -0
  120. package/dist/utils/path.d.ts.map +1 -0
  121. package/dist/utils/path.js +109 -0
  122. package/dist/utils/skillParser.d.ts +18 -0
  123. package/dist/utils/skillParser.d.ts.map +1 -0
  124. package/dist/utils/skillParser.js +147 -0
  125. package/dist/utils/stringUtils.d.ts +13 -0
  126. package/dist/utils/stringUtils.d.ts.map +1 -0
  127. package/dist/utils/stringUtils.js +44 -0
  128. package/package.json +51 -0
  129. package/src/agent.ts +405 -0
  130. package/src/hooks/executor.ts +440 -0
  131. package/src/hooks/index.ts +52 -0
  132. package/src/hooks/manager.ts +618 -0
  133. package/src/hooks/matcher.ts +187 -0
  134. package/src/hooks/settings.ts +129 -0
  135. package/src/hooks/types.ts +169 -0
  136. package/src/index.ts +24 -0
  137. package/src/managers/aiManager.ts +573 -0
  138. package/src/managers/backgroundBashManager.ts +203 -0
  139. package/src/managers/bashManager.ts +97 -0
  140. package/src/managers/mcpManager.ts +493 -0
  141. package/src/managers/messageManager.ts +415 -0
  142. package/src/managers/skillManager.ts +404 -0
  143. package/src/managers/slashCommandManager.ts +293 -0
  144. package/src/managers/toolManager.ts +106 -0
  145. package/src/services/aiService.ts +252 -0
  146. package/src/services/memory.ts +149 -0
  147. package/src/services/session.ts +265 -0
  148. package/src/tools/bashTool.ts +402 -0
  149. package/src/tools/deleteFileTool.ts +81 -0
  150. package/src/tools/editTool.ts +192 -0
  151. package/src/tools/globTool.ts +135 -0
  152. package/src/tools/grepTool.ts +326 -0
  153. package/src/tools/lsTool.ts +187 -0
  154. package/src/tools/multiEditTool.ts +268 -0
  155. package/src/tools/readTool.ts +165 -0
  156. package/src/tools/types.ts +47 -0
  157. package/src/tools/writeTool.ts +163 -0
  158. package/src/types.ts +260 -0
  159. package/src/utils/bashHistory.ts +303 -0
  160. package/src/utils/commandArgumentParser.ts +153 -0
  161. package/src/utils/constants.ts +37 -0
  162. package/src/utils/convertMessagesForAPI.ts +236 -0
  163. package/src/utils/customCommands.ts +85 -0
  164. package/src/utils/fileFilter.ts +202 -0
  165. package/src/utils/markdownParser.ts +156 -0
  166. package/src/utils/mcpUtils.ts +81 -0
  167. package/src/utils/messageOperations.ts +506 -0
  168. package/src/utils/path.ts +118 -0
  169. package/src/utils/skillParser.ts +188 -0
  170. package/src/utils/stringUtils.ts +50 -0
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Application constants definition
3
+ */
4
+
5
+ import path from "path";
6
+ import os from "os";
7
+
8
+ /**
9
+ * Application data storage directory
10
+ * Used to store debug logs, command history and other data
11
+ */
12
+ export const DATA_DIRECTORY = path.join(os.homedir(), ".wave");
13
+
14
+ /**
15
+ * Bash command history file path
16
+ */
17
+ export const BASH_HISTORY_FILE = path.join(DATA_DIRECTORY, "bash-history.json");
18
+
19
+ /**
20
+ * Error log directory path
21
+ */
22
+ export const ERROR_LOG_DIRECTORY = path.join(DATA_DIRECTORY, "error-logs");
23
+
24
+ /**
25
+ * User-level memory file path
26
+ */
27
+ export const USER_MEMORY_FILE = path.join(DATA_DIRECTORY, "user-memory.md");
28
+
29
+ /**
30
+ * AI related constants
31
+ */
32
+ export const DEFAULT_TOKEN_LIMIT = 64000; // Default token limit
33
+
34
+ export const FAST_MODEL_ID = process.env.AIGW_FAST_MODEL || "gemini-2.5-flash";
35
+
36
+ export const AGENT_MODEL_ID =
37
+ process.env.AIGW_MODEL || "claude-sonnet-4-20250514";
@@ -0,0 +1,236 @@
1
+ import type { Message } from "../types.js";
2
+ import { convertImageToBase64 } from "./messageOperations.js";
3
+ import { ChatCompletionMessageToolCall } from "openai/resources";
4
+ import { stripAnsiColors } from "./stringUtils.js";
5
+ import {
6
+ ChatCompletionContentPart,
7
+ ChatCompletionMessageParam,
8
+ } from "openai/resources.js";
9
+
10
+ /**
11
+ * Safely handle tool call parameters, ensuring a legal JSON string is returned
12
+ * @param args Tool call parameters
13
+ * @returns Legal JSON string
14
+ */
15
+ function safeToolArguments(args: string): string {
16
+ if (!args) {
17
+ return "{}";
18
+ }
19
+
20
+ try {
21
+ // Try to parse as JSON to validate format
22
+ JSON.parse(args);
23
+ return args;
24
+ } catch {
25
+ // logger.error(`Invalid tool arguments: ${args}`);
26
+ // If not valid JSON, return a fallback empty object
27
+ return "{}";
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Convert message format to API call format, stopping when a compressed message is encountered
33
+ * @param messages Message list
34
+ * @returns Converted API message format list
35
+ */
36
+ export function convertMessagesForAPI(
37
+ messages: Message[],
38
+ ): ChatCompletionMessageParam[] {
39
+ const recentMessages: ChatCompletionMessageParam[] = [];
40
+
41
+ const startIndex = messages.length - 1;
42
+ for (let i = startIndex; i >= 0; i--) {
43
+ const message = messages[i];
44
+
45
+ // Check if a compression block is encountered, if so, stop iteration
46
+ if (
47
+ message.role === "assistant" &&
48
+ message.blocks.some((block) => block.type === "compress")
49
+ ) {
50
+ // Add the content of the compression block as an assistant message to the history
51
+ const compressBlock = message.blocks.find(
52
+ (block) => block.type === "compress",
53
+ );
54
+ if (compressBlock && compressBlock.type === "compress") {
55
+ recentMessages.unshift({
56
+ role: "system",
57
+ content: `[Compressed Message Summary] ${compressBlock.content}`,
58
+ });
59
+ }
60
+ break;
61
+ }
62
+
63
+ // Skip empty assistant messages
64
+ if (message.role === "assistant" && message.blocks.length === 0) {
65
+ continue;
66
+ }
67
+
68
+ if (message.role === "assistant") {
69
+ // First check if there is a tool block, if so, add the tool message first
70
+ // Filter out incomplete tool blocks (no result or still running)
71
+ const toolBlocks = message.blocks.filter(
72
+ (block) => block.type === "tool",
73
+ );
74
+ const completedToolIds = new Set<string>(); // Record completed tool IDs
75
+
76
+ if (toolBlocks.length > 0) {
77
+ toolBlocks.forEach((toolBlock) => {
78
+ // Only add completed tool blocks (i.e., not running)
79
+ if (toolBlock.id && !toolBlock.isRunning) {
80
+ completedToolIds.add(toolBlock.id);
81
+
82
+ // Check for image data
83
+ if (toolBlock.images && toolBlock.images.length > 0) {
84
+ // If there is an image, create a user message instead of a tool message
85
+ const contentParts: ChatCompletionContentPart[] = [];
86
+
87
+ // Add tool result as text
88
+ const toolResultText = `Tool result for ${toolBlock.name || "unknown tool"}:\n${stripAnsiColors(toolBlock.result || "")}`;
89
+ contentParts.push({
90
+ type: "text",
91
+ text: toolResultText,
92
+ });
93
+
94
+ // Add image
95
+ toolBlock.images.forEach((image) => {
96
+ const imageUrl = image.data.startsWith("data:")
97
+ ? image.data
98
+ : `data:${image.mediaType || "image/png"};base64,${image.data}`;
99
+
100
+ contentParts.push({
101
+ type: "image_url",
102
+ image_url: {
103
+ url: imageUrl,
104
+ detail: "auto",
105
+ },
106
+ });
107
+ });
108
+
109
+ // Add user message
110
+ recentMessages.unshift({
111
+ role: "user",
112
+ content: contentParts,
113
+ });
114
+ } else {
115
+ // Normal tool message
116
+ recentMessages.unshift({
117
+ tool_call_id: toolBlock.id,
118
+ role: "tool",
119
+ content: stripAnsiColors(toolBlock.result || ""),
120
+ });
121
+ }
122
+ }
123
+ });
124
+ }
125
+
126
+ // Construct the content of the assistant message
127
+ let content = "";
128
+ let tool_calls: ChatCompletionMessageToolCall[] | undefined = undefined;
129
+
130
+ // Construct content from text blocks
131
+ const textBlocks = message.blocks.filter(
132
+ (block) => block.type === "text",
133
+ );
134
+ if (textBlocks.length > 0) {
135
+ content = textBlocks.map((block) => block.content || "").join("\n");
136
+ }
137
+
138
+ // Construct tool calls from tool blocks
139
+ if (toolBlocks.length > 0) {
140
+ tool_calls = toolBlocks
141
+ .filter(
142
+ (toolBlock) => toolBlock.id && completedToolIds.has(toolBlock.id),
143
+ )
144
+ .map((toolBlock) => ({
145
+ id: toolBlock.id!,
146
+ type: "function",
147
+ function: {
148
+ name: toolBlock.name || "",
149
+ arguments: safeToolArguments(
150
+ String(toolBlock.parameters || "{}"),
151
+ ),
152
+ },
153
+ }));
154
+
155
+ if (tool_calls.length === 0) {
156
+ tool_calls = undefined;
157
+ }
158
+ }
159
+
160
+ // Construct assistant message - only add if there is content or tool calls
161
+ if (content || tool_calls) {
162
+ const assistantMessage: ChatCompletionMessageParam = {
163
+ role: "assistant",
164
+ content,
165
+ tool_calls,
166
+ };
167
+
168
+ recentMessages.unshift(assistantMessage);
169
+ }
170
+ } else if (message.role === "user") {
171
+ // User messages converted to standard format
172
+ const contentParts: ChatCompletionContentPart[] = [];
173
+
174
+ message.blocks.forEach((block) => {
175
+ // Add text content
176
+ if (block.type === "text" && block.content) {
177
+ contentParts.push({
178
+ type: "text",
179
+ text: block.content,
180
+ });
181
+ }
182
+
183
+ // Handle custom command blocks - pass full content as text to AI
184
+ if (block.type === "custom_command" && block.content) {
185
+ contentParts.push({
186
+ type: "text",
187
+ text: block.content,
188
+ });
189
+ }
190
+
191
+ // If there is an image, add image content
192
+ if (
193
+ block.type === "image" &&
194
+ block.imageUrls &&
195
+ block.imageUrls.length > 0
196
+ ) {
197
+ block.imageUrls.forEach((imageUrl: string) => {
198
+ // Check if it's already base64, convert if not
199
+ let finalImageUrl = imageUrl;
200
+ if (!imageUrl.startsWith("data:image/")) {
201
+ // If it's a file path, it needs to be converted to base64
202
+ try {
203
+ finalImageUrl = convertImageToBase64(imageUrl);
204
+ } catch (error) {
205
+ console.error(
206
+ "Failed to convert image path to base64:",
207
+ imageUrl,
208
+ error,
209
+ );
210
+ // Skip this image, do not add to content
211
+ return;
212
+ }
213
+ }
214
+
215
+ contentParts.push({
216
+ type: "image_url",
217
+ image_url: {
218
+ url: finalImageUrl,
219
+ detail: "auto",
220
+ },
221
+ });
222
+ });
223
+ }
224
+ });
225
+
226
+ if (contentParts.length > 0) {
227
+ recentMessages.unshift({
228
+ role: "user",
229
+ content: contentParts,
230
+ });
231
+ }
232
+ }
233
+ }
234
+
235
+ return recentMessages;
236
+ }
@@ -0,0 +1,85 @@
1
+ import { existsSync, readdirSync } from "fs";
2
+ import { join, extname, basename } from "path";
3
+ import { homedir } from "os";
4
+ import type { CustomSlashCommand } from "../types.js";
5
+ import { parseMarkdownFile } from "./markdownParser.js";
6
+
7
+ /**
8
+ * Get the project-specific commands directory
9
+ */
10
+ export function getProjectCommandsDir(workdir: string): string {
11
+ return join(workdir, ".wave", "commands");
12
+ }
13
+
14
+ /**
15
+ * Get the user-specific commands directory
16
+ */
17
+ export function getUserCommandsDir(): string {
18
+ return join(homedir(), ".wave", "commands");
19
+ }
20
+
21
+ /**
22
+ * Scan a directory for markdown command files
23
+ */
24
+ function scanCommandsDirectory(dirPath: string): CustomSlashCommand[] {
25
+ if (!existsSync(dirPath)) {
26
+ return [];
27
+ }
28
+
29
+ const commands: CustomSlashCommand[] = [];
30
+
31
+ try {
32
+ const files = readdirSync(dirPath);
33
+
34
+ for (const file of files) {
35
+ if (extname(file) !== ".md") {
36
+ continue;
37
+ }
38
+
39
+ const filePath = join(dirPath, file);
40
+ const commandName = basename(file, ".md");
41
+
42
+ try {
43
+ const { content, config } = parseMarkdownFile(filePath);
44
+
45
+ commands.push({
46
+ id: commandName,
47
+ name: commandName,
48
+ description: config?.description, // Use description from frontmatter
49
+ filePath,
50
+ content,
51
+ config,
52
+ });
53
+ } catch (error) {
54
+ console.warn(`Failed to load custom command from ${filePath}:`, error);
55
+ }
56
+ }
57
+ } catch (error) {
58
+ console.warn(`Failed to scan commands directory ${dirPath}:`, error);
59
+ }
60
+
61
+ return commands;
62
+ }
63
+
64
+ /**
65
+ * Load all custom slash commands from both project and user directories
66
+ */
67
+ export function loadCustomSlashCommands(workdir: string): CustomSlashCommand[] {
68
+ const projectCommands = scanCommandsDirectory(getProjectCommandsDir(workdir));
69
+ const userCommands = scanCommandsDirectory(getUserCommandsDir());
70
+
71
+ // Project commands take precedence over user commands with the same name
72
+ const commandMap = new Map<string, CustomSlashCommand>();
73
+
74
+ // Add user commands first
75
+ for (const command of userCommands) {
76
+ commandMap.set(command.id, command);
77
+ }
78
+
79
+ // Add project commands (will overwrite user commands with same name)
80
+ for (const command of projectCommands) {
81
+ commandMap.set(command.id, command);
82
+ }
83
+
84
+ return Array.from(commandMap.values());
85
+ }
@@ -0,0 +1,202 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+
4
+ /**
5
+ * Common ignore directory and file patterns
6
+ * Can be reused by multiple tools (glob, ripgrep, etc.)
7
+ */
8
+ export const COMMON_IGNORE_PATTERNS = {
9
+ // Dependencies and build directories
10
+ dependencies: [
11
+ "node_modules/**",
12
+ ".git/**",
13
+ "dist/**",
14
+ "build/**",
15
+ ".next/**",
16
+ "coverage/**",
17
+ ".nyc_output/**",
18
+ "tmp/**",
19
+ "temp/**",
20
+ ],
21
+
22
+ // Cache and temporary files
23
+ cache: ["*.log", "*.cache", ".DS_Store", "Thumbs.db", "*~", "*.swp", "*.swo"],
24
+
25
+ // Editor and IDE files
26
+ editor: [".vscode/**", ".idea/**", "*.sublime-*"],
27
+
28
+ // Operating system related
29
+ os: [".DS_Store", "Thumbs.db", "desktop.ini"],
30
+ };
31
+
32
+ /**
33
+ * Get flat array of all common ignore patterns
34
+ */
35
+ export const getAllIgnorePatterns = (): string[] => {
36
+ return [
37
+ ...COMMON_IGNORE_PATTERNS.dependencies,
38
+ ...COMMON_IGNORE_PATTERNS.cache,
39
+ ...COMMON_IGNORE_PATTERNS.editor,
40
+ ...COMMON_IGNORE_PATTERNS.os,
41
+ ];
42
+ };
43
+
44
+ /**
45
+ * Recursively find all .gitignore files in directory
46
+ * @param dir Directory to search
47
+ * @param maxDepth Maximum recursion depth to prevent too deep searches
48
+ * @returns Array of .gitignore file paths
49
+ */
50
+ const findAllGitignoreFiles = (dir: string, maxDepth: number = 5): string[] => {
51
+ const gitignoreFiles: string[] = [];
52
+
53
+ if (maxDepth <= 0) return gitignoreFiles;
54
+
55
+ try {
56
+ const items = fs.readdirSync(dir, { withFileTypes: true });
57
+
58
+ for (const item of items) {
59
+ const fullPath = path.join(dir, item.name);
60
+
61
+ if (item.isFile() && item.name === ".gitignore") {
62
+ gitignoreFiles.push(fullPath);
63
+ } else if (item.isDirectory() && !shouldSkipDirectory(item.name)) {
64
+ // Recursively search subdirectories, but skip some obviously unnecessary directories
65
+ gitignoreFiles.push(...findAllGitignoreFiles(fullPath, maxDepth - 1));
66
+ }
67
+ }
68
+ } catch {
69
+ // Ignore permission errors and other issues
70
+ }
71
+
72
+ return gitignoreFiles;
73
+ };
74
+
75
+ /**
76
+ * Determine whether to skip searching a directory
77
+ */
78
+ const shouldSkipDirectory = (dirName: string): boolean => {
79
+ const skipDirs = [
80
+ "node_modules",
81
+ ".git",
82
+ "dist",
83
+ "build",
84
+ ".next",
85
+ "coverage",
86
+ ".nyc_output",
87
+ "tmp",
88
+ "temp",
89
+ ".cache",
90
+ ];
91
+ return skipDirs.includes(dirName);
92
+ };
93
+
94
+ /**
95
+ * Parse single .gitignore file content
96
+ * @param gitignorePath .gitignore file path
97
+ * @param basePath Base path for calculating relative paths
98
+ * @returns Array of parsed glob patterns
99
+ */
100
+ const parseGitignoreFile = (
101
+ gitignorePath: string,
102
+ basePath: string,
103
+ ): string[] => {
104
+ const patterns: string[] = [];
105
+
106
+ try {
107
+ if (fs.existsSync(gitignorePath)) {
108
+ const gitignoreContent = fs.readFileSync(gitignorePath, "utf8");
109
+ const gitignoreDir = path.dirname(gitignorePath);
110
+ // Calculate relative directory relative to base path
111
+ const relativeDirFromBase = path.relative(basePath, gitignoreDir);
112
+
113
+ const lines = gitignoreContent
114
+ .split("\n")
115
+ .map((line) => line.trim())
116
+ .filter((line) => line && !line.startsWith("#"));
117
+
118
+ for (const line of lines) {
119
+ // Skip negation rules (starting with !)
120
+ if (line.startsWith("!")) {
121
+ continue;
122
+ }
123
+
124
+ let pattern = line;
125
+
126
+ // Handle patterns starting with / (relative to current .gitignore file directory)
127
+ if (pattern.startsWith("/")) {
128
+ pattern = pattern.slice(1); // Remove leading /
129
+ }
130
+
131
+ // If .gitignore is in subdirectory, need to add path prefix
132
+ if (relativeDirFromBase && relativeDirFromBase !== ".") {
133
+ pattern = path.posix.join(relativeDirFromBase, pattern);
134
+ }
135
+
136
+ // If directory pattern (ending with /)
137
+ if (pattern.endsWith("/")) {
138
+ const dirName = pattern.slice(0, -1);
139
+ // For directory patterns, add both exact match and wildcard match
140
+ patterns.push(`${dirName}/**`); // Directory and all its sub-content
141
+ // If no path separators, it's a simple directory name, add global match
142
+ if (!dirName.includes("/") && !dirName.includes("*")) {
143
+ patterns.push(`**/${dirName}/**`); // Match directories of same name at any level
144
+ }
145
+ } else {
146
+ // File pattern
147
+ patterns.push(pattern);
148
+ // If no wildcards and no extension, also treat as directory
149
+ if (!pattern.includes("*") && !pattern.includes(".")) {
150
+ patterns.push(`${pattern}/**`);
151
+ // Also add global match for simple directory names
152
+ if (!pattern.includes("/")) {
153
+ patterns.push(`**/${pattern}/**`);
154
+ }
155
+ }
156
+ }
157
+ }
158
+ }
159
+ } catch {
160
+ // Ignore errors when reading .gitignore files
161
+ }
162
+
163
+ return patterns;
164
+ };
165
+
166
+ /**
167
+ * Parse all .gitignore files in the working directory and its subdirectories and convert to glob patterns
168
+ * @param workdir Working directory
169
+ * @returns Array of glob ignore patterns
170
+ */
171
+ export const parseGitignoreToGlob = (workdir: string): string[] => {
172
+ const patterns: string[] = [];
173
+
174
+ try {
175
+ // Find all .gitignore files
176
+ const gitignoreFiles = findAllGitignoreFiles(workdir);
177
+
178
+ // Parse each .gitignore file
179
+ for (const gitignoreFile of gitignoreFiles) {
180
+ patterns.push(...parseGitignoreFile(gitignoreFile, workdir));
181
+ }
182
+ } catch {
183
+ // Ignore errors during search process
184
+ }
185
+
186
+ return patterns;
187
+ };
188
+
189
+ /**
190
+ * Get ignore patterns for glob search
191
+ * @param workdir Working directory for resolving .gitignore files
192
+ */
193
+ export const getGlobIgnorePatterns = (workdir?: string): string[] => {
194
+ const patterns = getAllIgnorePatterns();
195
+
196
+ // If working directory is provided, parse .gitignore files
197
+ if (workdir) {
198
+ patterns.push(...parseGitignoreToGlob(workdir));
199
+ }
200
+
201
+ return patterns;
202
+ };