@probelabs/probe 0.6.0-rc203 → 0.6.0-rc205
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.
- package/bin/binaries/probe-v0.6.0-rc205-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc205-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc205-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc205-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc205-x86_64-unknown-linux-musl.tar.gz +0 -0
- package/build/agent/ProbeAgent.d.ts +2 -0
- package/build/agent/ProbeAgent.js +233 -40
- package/build/agent/index.js +1566 -84
- package/build/agent/simpleTelemetry.js +12 -0
- package/build/agent/tasks/TaskManager.js +604 -0
- package/build/agent/tasks/index.js +15 -0
- package/build/agent/tasks/taskTool.js +476 -0
- package/build/agent/tools.js +11 -0
- package/build/delegate.js +7 -2
- package/build/index.js +14 -1
- package/build/search.js +19 -5
- package/build/tools/common.js +67 -0
- package/build/tools/vercel.js +28 -12
- package/build/utils/error-types.js +303 -0
- package/build/utils/path-validation.js +21 -3
- package/cjs/agent/ProbeAgent.cjs +8940 -6393
- package/cjs/agent/simpleTelemetry.cjs +10 -0
- package/cjs/index.cjs +8960 -6393
- package/package.json +2 -2
- package/src/agent/ProbeAgent.d.ts +2 -0
- package/src/agent/ProbeAgent.js +233 -40
- package/src/agent/index.js +14 -2
- package/src/agent/simpleTelemetry.js +12 -0
- package/src/agent/tasks/TaskManager.js +604 -0
- package/src/agent/tasks/index.js +15 -0
- package/src/agent/tasks/taskTool.js +476 -0
- package/src/agent/tools.js +11 -0
- package/src/delegate.js +7 -2
- package/src/index.js +14 -1
- package/src/search.js +19 -5
- package/src/tools/common.js +67 -0
- package/src/tools/vercel.js +28 -12
- package/src/utils/error-types.js +303 -0
- package/src/utils/path-validation.js +21 -3
- package/bin/binaries/probe-v0.6.0-rc203-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc203-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc203-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc203-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc203-x86_64-unknown-linux-musl.tar.gz +0 -0
package/build/tools/vercel.js
CHANGED
|
@@ -9,6 +9,7 @@ import { query } from '../query.js';
|
|
|
9
9
|
import { extract } from '../extract.js';
|
|
10
10
|
import { delegate } from '../delegate.js';
|
|
11
11
|
import { searchSchema, querySchema, extractSchema, delegateSchema, searchDescription, queryDescription, extractDescription, delegateDescription, parseTargets, parseAndResolvePaths, resolveTargetPath } from './common.js';
|
|
12
|
+
import { formatErrorForAI } from '../utils/error-types.js';
|
|
12
13
|
|
|
13
14
|
const CODE_SEARCH_SCHEMA = {
|
|
14
15
|
type: 'object',
|
|
@@ -117,15 +118,29 @@ function parseDelegatedTargets(rawResponse) {
|
|
|
117
118
|
|
|
118
119
|
function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, allowTests }) {
|
|
119
120
|
return [
|
|
120
|
-
'You are a code-search subagent. Your
|
|
121
|
-
'
|
|
122
|
-
'
|
|
123
|
-
'
|
|
124
|
-
|
|
121
|
+
'You are a code-search subagent. Your job is to find ALL relevant code locations for the given query.',
|
|
122
|
+
'',
|
|
123
|
+
'The query may be complex - it could be a natural language question, a multi-part request, or a simple keyword.',
|
|
124
|
+
'Break down complex queries into multiple searches to cover all aspects.',
|
|
125
|
+
'',
|
|
126
|
+
'Available tools:',
|
|
127
|
+
'- search: Find code matching keywords or patterns. Run multiple searches for different aspects of complex queries.',
|
|
128
|
+
'- extract: Verify code snippets to ensure targets are actually relevant before including them.',
|
|
129
|
+
'- listFiles: Understand directory structure to find where relevant code might live.',
|
|
130
|
+
'',
|
|
131
|
+
'Strategy for complex queries:',
|
|
132
|
+
'1. Analyze the query - identify key concepts, entities, and relationships',
|
|
133
|
+
'2. Run focused searches for each concept (e.g., "error handling" + "authentication" separately)',
|
|
134
|
+
'3. Use extract to verify relevance of promising results',
|
|
135
|
+
'4. Combine all relevant targets in your final response',
|
|
136
|
+
'',
|
|
137
|
+
`Query: ${searchQuery}`,
|
|
125
138
|
`Search path(s): ${searchPath}`,
|
|
126
139
|
`Options: exact=${exact ? 'true' : 'false'}, language=${language || 'auto'}, allow_tests=${allowTests ? 'true' : 'false'}.`,
|
|
127
|
-
'
|
|
128
|
-
'
|
|
140
|
+
'',
|
|
141
|
+
'Return ONLY valid JSON: {"targets": ["path/to/file.ext#Symbol", "path/to/file.ext:line", "path/to/file.ext:start-end"]}',
|
|
142
|
+
'Prefer #Symbol when a function/class name is clear; otherwise use line numbers.',
|
|
143
|
+
'Deduplicate targets. Do NOT explain or answer - ONLY return the JSON targets.'
|
|
129
144
|
].join('\n');
|
|
130
145
|
}
|
|
131
146
|
|
|
@@ -200,7 +215,7 @@ export const searchTool = (options = {}) => {
|
|
|
200
215
|
return await runRawSearch();
|
|
201
216
|
} catch (error) {
|
|
202
217
|
console.error('Error executing search command:', error);
|
|
203
|
-
return
|
|
218
|
+
return formatErrorForAI(error);
|
|
204
219
|
}
|
|
205
220
|
}
|
|
206
221
|
|
|
@@ -230,7 +245,7 @@ export const searchTool = (options = {}) => {
|
|
|
230
245
|
bashConfig: null,
|
|
231
246
|
architectureFileName: options.architectureFileName || null,
|
|
232
247
|
promptType: 'code-searcher',
|
|
233
|
-
allowedTools: ['search', 'attempt_completion'],
|
|
248
|
+
allowedTools: ['search', 'extract', 'listFiles', 'attempt_completion'],
|
|
234
249
|
searchDelegate: false,
|
|
235
250
|
schema: CODE_SEARCH_SCHEMA
|
|
236
251
|
});
|
|
@@ -269,7 +284,8 @@ export const searchTool = (options = {}) => {
|
|
|
269
284
|
return await runRawSearch();
|
|
270
285
|
} catch (fallbackError) {
|
|
271
286
|
console.error('Error executing search command:', fallbackError);
|
|
272
|
-
|
|
287
|
+
// Both delegation and fallback failed - provide detailed error
|
|
288
|
+
return formatErrorForAI(fallbackError);
|
|
273
289
|
}
|
|
274
290
|
}
|
|
275
291
|
}
|
|
@@ -322,7 +338,7 @@ export const queryTool = (options = {}) => {
|
|
|
322
338
|
return results;
|
|
323
339
|
} catch (error) {
|
|
324
340
|
console.error('Error executing query command:', error);
|
|
325
|
-
return
|
|
341
|
+
return formatErrorForAI(error);
|
|
326
342
|
}
|
|
327
343
|
}
|
|
328
344
|
});
|
|
@@ -433,7 +449,7 @@ export const extractTool = (options = {}) => {
|
|
|
433
449
|
return results;
|
|
434
450
|
} catch (error) {
|
|
435
451
|
console.error('Error executing extract command:', error);
|
|
436
|
-
return
|
|
452
|
+
return formatErrorForAI(error);
|
|
437
453
|
}
|
|
438
454
|
}
|
|
439
455
|
});
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured error types for AI-friendly error handling
|
|
3
|
+
* @module utils/error-types
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Error categories for classification
|
|
8
|
+
*/
|
|
9
|
+
export const ErrorCategory = {
|
|
10
|
+
PATH_ERROR: 'path_error', // Path doesn't exist, not a directory, permission denied
|
|
11
|
+
PARAMETER_ERROR: 'parameter_error', // Invalid parameters provided
|
|
12
|
+
TIMEOUT_ERROR: 'timeout_error', // Operation timed out
|
|
13
|
+
API_ERROR: 'api_error', // AI provider errors (rate limit, token limit)
|
|
14
|
+
DELEGATION_ERROR: 'delegation_error', // Subagent failures
|
|
15
|
+
INTERNAL_ERROR: 'internal_error' // Unexpected system errors
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Escape special characters for XML output
|
|
20
|
+
* @param {string} str - String to escape
|
|
21
|
+
* @returns {string} - Escaped string
|
|
22
|
+
*/
|
|
23
|
+
function escapeXml(str) {
|
|
24
|
+
return String(str)
|
|
25
|
+
.replace(/&/g, '&')
|
|
26
|
+
.replace(/</g, '<')
|
|
27
|
+
.replace(/>/g, '>')
|
|
28
|
+
.replace(/"/g, '"')
|
|
29
|
+
.replace(/'/g, ''');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Base class for structured errors with AI-friendly information
|
|
34
|
+
*/
|
|
35
|
+
export class ProbeError extends Error {
|
|
36
|
+
/**
|
|
37
|
+
* Create a ProbeError
|
|
38
|
+
* @param {string} message - Error message
|
|
39
|
+
* @param {Object} options - Options
|
|
40
|
+
* @param {string} [options.category] - Error category from ErrorCategory
|
|
41
|
+
* @param {boolean} [options.recoverable] - Whether the error is recoverable by AI action
|
|
42
|
+
* @param {string} [options.suggestion] - Suggested action for recovery
|
|
43
|
+
* @param {Object} [options.details] - Additional error details
|
|
44
|
+
* @param {Error} [options.originalError] - Original error that was wrapped
|
|
45
|
+
*/
|
|
46
|
+
constructor(message, options = {}) {
|
|
47
|
+
super(message);
|
|
48
|
+
this.name = 'ProbeError';
|
|
49
|
+
this.category = options.category || ErrorCategory.INTERNAL_ERROR;
|
|
50
|
+
this.recoverable = options.recoverable ?? false;
|
|
51
|
+
this.suggestion = options.suggestion || null;
|
|
52
|
+
this.details = options.details || {};
|
|
53
|
+
this.originalError = options.originalError || null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Format error as XML for AI consumption
|
|
58
|
+
* @returns {string} - XML-formatted error
|
|
59
|
+
*/
|
|
60
|
+
toXml() {
|
|
61
|
+
const parts = [
|
|
62
|
+
`<error type="${this.category}" recoverable="${this.recoverable}">`,
|
|
63
|
+
`<message>${escapeXml(this.message)}</message>`
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
if (this.suggestion) {
|
|
67
|
+
parts.push(`<suggestion>${escapeXml(this.suggestion)}</suggestion>`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (Object.keys(this.details).length > 0) {
|
|
71
|
+
parts.push(`<details>${escapeXml(JSON.stringify(this.details))}</details>`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
parts.push('</error>');
|
|
75
|
+
return parts.join('\n');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Format error for plain text (backward compatible)
|
|
80
|
+
* @returns {string} - Plain text error
|
|
81
|
+
*/
|
|
82
|
+
toString() {
|
|
83
|
+
return `Error: ${this.message}`;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Path-related errors (not found, not a directory, permission denied)
|
|
89
|
+
*/
|
|
90
|
+
export class PathError extends ProbeError {
|
|
91
|
+
constructor(message, options = {}) {
|
|
92
|
+
super(message, {
|
|
93
|
+
...options,
|
|
94
|
+
category: ErrorCategory.PATH_ERROR,
|
|
95
|
+
recoverable: options.recoverable ?? true,
|
|
96
|
+
suggestion: options.suggestion || 'Please verify the path exists or try searching in a different directory.'
|
|
97
|
+
});
|
|
98
|
+
this.name = 'PathError';
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Parameter validation errors
|
|
104
|
+
*/
|
|
105
|
+
export class ParameterError extends ProbeError {
|
|
106
|
+
constructor(message, options = {}) {
|
|
107
|
+
super(message, {
|
|
108
|
+
...options,
|
|
109
|
+
category: ErrorCategory.PARAMETER_ERROR,
|
|
110
|
+
recoverable: options.recoverable ?? true,
|
|
111
|
+
suggestion: options.suggestion || 'Please check and correct the parameter values.'
|
|
112
|
+
});
|
|
113
|
+
this.name = 'ParameterError';
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Timeout errors
|
|
119
|
+
*/
|
|
120
|
+
export class TimeoutError extends ProbeError {
|
|
121
|
+
constructor(message, options = {}) {
|
|
122
|
+
super(message, {
|
|
123
|
+
...options,
|
|
124
|
+
category: ErrorCategory.TIMEOUT_ERROR,
|
|
125
|
+
recoverable: options.recoverable ?? true,
|
|
126
|
+
suggestion: options.suggestion || 'The operation timed out. Try a more specific query or increase the timeout.'
|
|
127
|
+
});
|
|
128
|
+
this.name = 'TimeoutError';
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* API/AI provider errors (rate limit, token limit, etc.)
|
|
134
|
+
*/
|
|
135
|
+
export class ApiError extends ProbeError {
|
|
136
|
+
constructor(message, options = {}) {
|
|
137
|
+
super(message, {
|
|
138
|
+
...options,
|
|
139
|
+
category: ErrorCategory.API_ERROR,
|
|
140
|
+
recoverable: options.recoverable ?? false,
|
|
141
|
+
suggestion: options.suggestion || 'This is an API provider error. The system will retry automatically if possible.'
|
|
142
|
+
});
|
|
143
|
+
this.name = 'ApiError';
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Delegation/subagent errors
|
|
149
|
+
*/
|
|
150
|
+
export class DelegationError extends ProbeError {
|
|
151
|
+
constructor(message, options = {}) {
|
|
152
|
+
super(message, {
|
|
153
|
+
...options,
|
|
154
|
+
category: ErrorCategory.DELEGATION_ERROR,
|
|
155
|
+
recoverable: options.recoverable ?? true,
|
|
156
|
+
suggestion: options.suggestion || 'The delegated task failed. Consider breaking down the task further or trying a different approach.'
|
|
157
|
+
});
|
|
158
|
+
this.name = 'DelegationError';
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Categorize any error into a structured ProbeError
|
|
164
|
+
* @param {Error|string} error - Error to categorize
|
|
165
|
+
* @returns {ProbeError} - Structured error with category and suggestions
|
|
166
|
+
*/
|
|
167
|
+
export function categorizeError(error) {
|
|
168
|
+
// If already a ProbeError, return as-is
|
|
169
|
+
if (error instanceof ProbeError) {
|
|
170
|
+
return error;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const message = error?.message || String(error);
|
|
174
|
+
const lowerMessage = message.toLowerCase();
|
|
175
|
+
// error.code could be a string (like 'ENOENT') or a number (like 429)
|
|
176
|
+
const errorCode = error?.code != null ? String(error.code).toLowerCase() : '';
|
|
177
|
+
|
|
178
|
+
// Path-related errors
|
|
179
|
+
if (lowerMessage.includes('path does not exist') ||
|
|
180
|
+
lowerMessage.includes('no such file or directory') ||
|
|
181
|
+
errorCode === 'enoent') {
|
|
182
|
+
return new PathError(message, {
|
|
183
|
+
originalError: error,
|
|
184
|
+
suggestion: 'The specified path does not exist. Please verify the path or use a different directory.'
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (lowerMessage.includes('not a directory') || errorCode === 'enotdir') {
|
|
189
|
+
return new PathError(message, {
|
|
190
|
+
originalError: error,
|
|
191
|
+
suggestion: 'The path is not a directory. Please provide a valid directory path.'
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (lowerMessage.includes('permission denied') || errorCode === 'eacces') {
|
|
196
|
+
return new PathError(message, {
|
|
197
|
+
originalError: error,
|
|
198
|
+
recoverable: false,
|
|
199
|
+
suggestion: 'Permission denied. This is a system-level restriction that cannot be resolved by changing the query.'
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Timeout errors
|
|
204
|
+
if (lowerMessage.includes('timed out') ||
|
|
205
|
+
lowerMessage.includes('timeout') ||
|
|
206
|
+
errorCode === 'etimedout' ||
|
|
207
|
+
error?.killed === true) {
|
|
208
|
+
return new TimeoutError(message, {
|
|
209
|
+
originalError: error,
|
|
210
|
+
suggestion: 'The operation timed out. Try a more specific query, reduce the search scope, or increase the timeout.'
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// API errors - rate limiting and overload
|
|
215
|
+
const rateLimitPatterns = ['rate_limit', 'rate limit', '429', 'too many requests', 'overloaded'];
|
|
216
|
+
if (rateLimitPatterns.some(p => lowerMessage.includes(p))) {
|
|
217
|
+
return new ApiError(message, {
|
|
218
|
+
originalError: error,
|
|
219
|
+
recoverable: true,
|
|
220
|
+
suggestion: 'API rate limit or overload encountered. The system will retry automatically with backoff.'
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// API errors - server errors
|
|
225
|
+
const serverErrorPatterns = ['500', '502', '503', '504', 'internal server error', 'bad gateway', 'service unavailable'];
|
|
226
|
+
if (serverErrorPatterns.some(p => lowerMessage.includes(p))) {
|
|
227
|
+
return new ApiError(message, {
|
|
228
|
+
originalError: error,
|
|
229
|
+
recoverable: true,
|
|
230
|
+
suggestion: 'API server error encountered. The system will retry automatically.'
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// API errors - context/token limits
|
|
235
|
+
if ((lowerMessage.includes('context') || lowerMessage.includes('token')) &&
|
|
236
|
+
(lowerMessage.includes('limit') || lowerMessage.includes('exceed'))) {
|
|
237
|
+
return new ApiError(message, {
|
|
238
|
+
originalError: error,
|
|
239
|
+
recoverable: true,
|
|
240
|
+
suggestion: 'Context or token limit exceeded. The conversation may be automatically compacted.'
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Parameter validation errors (check before API auth to catch 'invalid parameter' first)
|
|
245
|
+
if (lowerMessage.includes('invalid parameter') ||
|
|
246
|
+
lowerMessage.includes('missing parameter') ||
|
|
247
|
+
lowerMessage.includes('required') ||
|
|
248
|
+
lowerMessage.includes('must be')) {
|
|
249
|
+
return new ParameterError(message, {
|
|
250
|
+
originalError: error
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// API errors - authentication (after parameter errors to avoid catching 'invalid parameter')
|
|
255
|
+
if (lowerMessage.includes('invalid') && (lowerMessage.includes('api') || lowerMessage.includes('key') || lowerMessage.includes('auth'))) {
|
|
256
|
+
return new ApiError(message, {
|
|
257
|
+
originalError: error,
|
|
258
|
+
recoverable: false,
|
|
259
|
+
suggestion: 'API authentication error. Please check the API key configuration.'
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Delegation errors
|
|
264
|
+
if (lowerMessage.includes('delegation failed') ||
|
|
265
|
+
lowerMessage.includes('delegate agent') ||
|
|
266
|
+
lowerMessage.includes('subagent')) {
|
|
267
|
+
return new DelegationError(message, {
|
|
268
|
+
originalError: error
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Network errors
|
|
273
|
+
if (errorCode === 'econnreset' ||
|
|
274
|
+
errorCode === 'econnrefused' ||
|
|
275
|
+
errorCode === 'enotfound' ||
|
|
276
|
+
lowerMessage.includes('network') ||
|
|
277
|
+
lowerMessage.includes('connection')) {
|
|
278
|
+
return new ApiError(message, {
|
|
279
|
+
originalError: error,
|
|
280
|
+
recoverable: true,
|
|
281
|
+
suggestion: 'Network error encountered. The system will retry automatically.'
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Default: internal error
|
|
286
|
+
return new ProbeError(message, {
|
|
287
|
+
category: ErrorCategory.INTERNAL_ERROR,
|
|
288
|
+
recoverable: false,
|
|
289
|
+
originalError: error,
|
|
290
|
+
suggestion: 'An unexpected error occurred. Please check the error message for details.'
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Format an error for AI consumption as XML
|
|
296
|
+
* Works with both ProbeError and regular Error objects
|
|
297
|
+
* @param {Error} error - Error to format
|
|
298
|
+
* @returns {string} - XML-formatted error string
|
|
299
|
+
*/
|
|
300
|
+
export function formatErrorForAI(error) {
|
|
301
|
+
const structuredError = categorizeError(error);
|
|
302
|
+
return structuredError.toXml();
|
|
303
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import { promises as fs } from 'fs';
|
|
8
|
+
import { PathError } from './error-types.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Validates and normalizes a path to be used as working directory (cwd).
|
|
@@ -18,7 +19,7 @@ import { promises as fs } from 'fs';
|
|
|
18
19
|
* @param {string} inputPath - The path to validate
|
|
19
20
|
* @param {string} [defaultPath] - Default path to use if inputPath is not provided
|
|
20
21
|
* @returns {Promise<string>} Normalized absolute path
|
|
21
|
-
* @throws {
|
|
22
|
+
* @throws {PathError} If the path is invalid or doesn't exist
|
|
22
23
|
*/
|
|
23
24
|
export async function validateCwdPath(inputPath, defaultPath = process.cwd()) {
|
|
24
25
|
// Use default if not provided
|
|
@@ -32,11 +33,28 @@ export async function validateCwdPath(inputPath, defaultPath = process.cwd()) {
|
|
|
32
33
|
try {
|
|
33
34
|
const stats = await fs.stat(normalizedPath);
|
|
34
35
|
if (!stats.isDirectory()) {
|
|
35
|
-
throw new
|
|
36
|
+
throw new PathError(`Path is not a directory: ${normalizedPath}`, {
|
|
37
|
+
suggestion: 'The specified path is a file, not a directory. Please provide a directory path for searching.',
|
|
38
|
+
details: { path: normalizedPath, type: 'file' }
|
|
39
|
+
});
|
|
36
40
|
}
|
|
37
41
|
} catch (error) {
|
|
42
|
+
// Re-throw if already a PathError
|
|
43
|
+
if (error instanceof PathError) {
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
38
46
|
if (error.code === 'ENOENT') {
|
|
39
|
-
throw new
|
|
47
|
+
throw new PathError(`Path does not exist: ${normalizedPath}`, {
|
|
48
|
+
suggestion: 'The specified path does not exist. Please verify the path is correct or use a different directory.',
|
|
49
|
+
details: { path: normalizedPath }
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (error.code === 'EACCES') {
|
|
53
|
+
throw new PathError(`Permission denied: ${normalizedPath}`, {
|
|
54
|
+
recoverable: false,
|
|
55
|
+
suggestion: 'Permission denied accessing this path. This is a system-level restriction.',
|
|
56
|
+
details: { path: normalizedPath }
|
|
57
|
+
});
|
|
40
58
|
}
|
|
41
59
|
throw error;
|
|
42
60
|
}
|