@probelabs/probe 0.6.0-rc173 → 0.6.0-rc175

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.
@@ -31,15 +31,15 @@ export const searchTool = (options = {}) => {
31
31
  // Use parameter maxTokens if provided, otherwise use the default
32
32
  const effectiveMaxTokens = paramMaxTokens || maxTokens;
33
33
 
34
- // Use the path from parameters if provided, otherwise use defaultPath from config
35
- let searchPath = path || options.defaultPath || '.';
34
+ // Use the path from parameters if provided, otherwise use cwd from config
35
+ let searchPath = path || options.cwd || '.';
36
36
 
37
- // If path is "." or "./", use the defaultPath if available
38
- if ((searchPath === "." || searchPath === "./") && options.defaultPath) {
37
+ // If path is "." or "./", use the cwd if available
38
+ if ((searchPath === "." || searchPath === "./") && options.cwd) {
39
39
  if (debug) {
40
- console.error(`Using default path "${options.defaultPath}" instead of "${searchPath}"`);
40
+ console.error(`Using cwd "${options.cwd}" instead of "${searchPath}"`);
41
41
  }
42
- searchPath = options.defaultPath;
42
+ searchPath = options.cwd;
43
43
  }
44
44
 
45
45
  if (debug) {
@@ -49,6 +49,7 @@ export const searchTool = (options = {}) => {
49
49
  const searchOptions = {
50
50
  query: searchQuery,
51
51
  path: searchPath,
52
+ cwd: options.cwd, // Working directory for resolving relative paths
52
53
  allowTests: allow_tests,
53
54
  exact,
54
55
  json: false,
@@ -89,15 +90,15 @@ export const queryTool = (options = {}) => {
89
90
  inputSchema: querySchema,
90
91
  execute: async ({ pattern, path, language, allow_tests }) => {
91
92
  try {
92
- // Use the path from parameters if provided, otherwise use defaultPath from config
93
- let queryPath = path || options.defaultPath || '.';
93
+ // Use the path from parameters if provided, otherwise use cwd from config
94
+ let queryPath = path || options.cwd || '.';
94
95
 
95
- // If path is "." or "./", use the defaultPath if available
96
- if ((queryPath === "." || queryPath === "./") && options.defaultPath) {
96
+ // If path is "." or "./", use the cwd if available
97
+ if ((queryPath === "." || queryPath === "./") && options.cwd) {
97
98
  if (debug) {
98
- console.error(`Using default path "${options.defaultPath}" instead of "${queryPath}"`);
99
+ console.error(`Using cwd "${options.cwd}" instead of "${queryPath}"`);
99
100
  }
100
- queryPath = options.defaultPath;
101
+ queryPath = options.cwd;
101
102
  }
102
103
 
103
104
  if (debug) {
@@ -107,6 +108,7 @@ export const queryTool = (options = {}) => {
107
108
  const results = await query({
108
109
  pattern,
109
110
  path: queryPath,
111
+ cwd: options.cwd, // Working directory for resolving relative paths
110
112
  language,
111
113
  allow_tests,
112
114
  json: false
@@ -137,28 +139,20 @@ export const extractTool = (options = {}) => {
137
139
  inputSchema: extractSchema,
138
140
  execute: async ({ targets, input_content, line, end_line, allow_tests, context_lines, format }) => {
139
141
  try {
140
- // Use the defaultPath from config for context
141
- let extractPath = options.defaultPath || '.';
142
-
143
- // If path is "." or "./", use the defaultPath if available
144
- if ((extractPath === "." || extractPath === "./") && options.defaultPath) {
145
- if (debug) {
146
- console.error(`Using default path "${options.defaultPath}" instead of "${extractPath}"`);
147
- }
148
- extractPath = options.defaultPath;
149
- }
142
+ // Use the cwd from config for working directory
143
+ const effectiveCwd = options.cwd || '.';
150
144
 
151
145
  if (debug) {
152
146
  if (targets) {
153
- console.error(`Executing extract with targets: "${targets}", path: "${extractPath}", context lines: ${context_lines || 10}`);
147
+ console.error(`Executing extract with targets: "${targets}", cwd: "${effectiveCwd}", context lines: ${context_lines || 10}`);
154
148
  } else if (input_content) {
155
- console.error(`Executing extract with input content, path: "${extractPath}", context lines: ${context_lines || 10}`);
149
+ console.error(`Executing extract with input content, cwd: "${effectiveCwd}", context lines: ${context_lines || 10}`);
156
150
  }
157
151
  }
158
152
 
159
153
  // Create a temporary file for input content if provided
160
154
  let tempFilePath = null;
161
- let extractOptions = { path: extractPath };
155
+ let extractOptions = { cwd: effectiveCwd };
162
156
 
163
157
  if (input_content) {
164
158
  // Import required modules
@@ -184,6 +178,7 @@ export const extractTool = (options = {}) => {
184
178
  // Set up extract options with input file
185
179
  extractOptions = {
186
180
  inputFile: tempFilePath,
181
+ cwd: effectiveCwd,
187
182
  allowTests: allow_tests,
188
183
  contextLines: context_lines,
189
184
  format: effectiveFormat
@@ -202,6 +197,7 @@ export const extractTool = (options = {}) => {
202
197
  // Set up extract options with files
203
198
  extractOptions = {
204
199
  files,
200
+ cwd: effectiveCwd,
205
201
  allowTests: allow_tests,
206
202
  contextLines: context_lines,
207
203
  format: effectiveFormat
@@ -241,12 +237,12 @@ export const extractTool = (options = {}) => {
241
237
  * @param {Object} [options] - Configuration options
242
238
  * @param {boolean} [options.debug=false] - Enable debug logging
243
239
  * @param {number} [options.timeout=300] - Default timeout in seconds
244
- * @param {string} [options.defaultPath] - Default path to use if not specified in call
240
+ * @param {string} [options.cwd] - Working directory to use if not specified in call
245
241
  * @param {string[]} [options.allowedFolders] - Allowed folders for workspace isolation
246
242
  * @returns {Object} Configured delegate tool
247
243
  */
248
244
  export const delegateTool = (options = {}) => {
249
- const { debug = false, timeout = 300, defaultPath, allowedFolders } = options;
245
+ const { debug = false, timeout = 300, cwd, allowedFolders } = options;
250
246
 
251
247
  return tool({
252
248
  name: 'delegate',
@@ -289,8 +285,8 @@ export const delegateTool = (options = {}) => {
289
285
  }
290
286
 
291
287
  // Use inherited path if not specified in AI call
292
- // Priority: explicit path > defaultPath > first allowedFolder
293
- const effectivePath = path || defaultPath || (allowedFolders && allowedFolders[0]);
288
+ // Priority: explicit path > cwd > first allowedFolder
289
+ const effectivePath = path || cwd || (allowedFolders && allowedFolders[0]);
294
290
 
295
291
  if (debug) {
296
292
  console.error(`Executing delegate with task: "${task.substring(0, 100)}${task.length > 100 ? '...' : ''}"`);
@@ -7,6 +7,7 @@ import fs from 'fs';
7
7
  import path from 'path';
8
8
  import { promisify } from 'util';
9
9
  import { exec } from 'child_process';
10
+ import { getEntryTypeSync } from './symlink-utils.js';
10
11
 
11
12
  const execAsync = promisify(exec);
12
13
 
@@ -101,7 +102,10 @@ async function listFilesByLevelManually(directory, maxFiles, respectGitignore) {
101
102
  const entries = fs.readdirSync(dir, { withFileTypes: true });
102
103
 
103
104
  // Process files first (at current level)
104
- const files = entries.filter(entry => entry.isFile());
105
+ const files = entries.filter(entry => {
106
+ const fullPath = path.join(dir, entry.name);
107
+ return getEntryTypeSync(entry, fullPath).isFile;
108
+ });
105
109
  for (const file of files) {
106
110
  if (result.length >= maxFiles) break;
107
111
 
@@ -115,7 +119,10 @@ async function listFilesByLevelManually(directory, maxFiles, respectGitignore) {
115
119
  }
116
120
 
117
121
  // Then add directories to queue for next level
118
- const dirs = entries.filter(entry => entry.isDirectory());
122
+ const dirs = entries.filter(entry => {
123
+ const fullPath = path.join(dir, entry.name);
124
+ return getEntryTypeSync(entry, fullPath).isDirectory;
125
+ });
119
126
  for (const subdir of dirs) {
120
127
  const subdirPath = path.join(dir, subdir.name);
121
128
  const relativeSubdirPath = path.relative(directory, subdirPath);
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Path validation utilities for the probe package
3
+ * @module utils/path-validation
4
+ */
5
+
6
+ import path from 'path';
7
+ import { promises as fs } from 'fs';
8
+
9
+ /**
10
+ * Validates and normalizes a path to be used as working directory (cwd).
11
+ *
12
+ * Security considerations:
13
+ * - Normalizes path to resolve '..' and '.' components
14
+ * - Returns absolute path to prevent ambiguity
15
+ * - Does NOT restrict access to specific directories (that's the responsibility
16
+ * of higher-level components like ProbeAgent with allowedFolders)
17
+ *
18
+ * @param {string} inputPath - The path to validate
19
+ * @param {string} [defaultPath] - Default path to use if inputPath is not provided
20
+ * @returns {Promise<string>} Normalized absolute path
21
+ * @throws {Error} If the path is invalid or doesn't exist
22
+ */
23
+ export async function validateCwdPath(inputPath, defaultPath = process.cwd()) {
24
+ // Use default if not provided
25
+ const targetPath = inputPath || defaultPath;
26
+
27
+ // Normalize and resolve to absolute path
28
+ // This handles '..' traversal and makes the path unambiguous
29
+ const normalizedPath = path.normalize(path.resolve(targetPath));
30
+
31
+ // Verify the path exists and is a directory
32
+ try {
33
+ const stats = await fs.stat(normalizedPath);
34
+ if (!stats.isDirectory()) {
35
+ throw new Error(`Path is not a directory: ${normalizedPath}`);
36
+ }
37
+ } catch (error) {
38
+ if (error.code === 'ENOENT') {
39
+ throw new Error(`Path does not exist: ${normalizedPath}`);
40
+ }
41
+ throw error;
42
+ }
43
+
44
+ return normalizedPath;
45
+ }
46
+
47
+ /**
48
+ * Validates a path option without requiring it to exist.
49
+ * Use this for paths that might be created or are optional.
50
+ *
51
+ * @param {string} inputPath - The path to validate
52
+ * @param {string} [defaultPath] - Default path to use if inputPath is not provided
53
+ * @returns {string} Normalized absolute path
54
+ */
55
+ export function normalizePath(inputPath, defaultPath = process.cwd()) {
56
+ const targetPath = inputPath || defaultPath;
57
+ return path.normalize(path.resolve(targetPath));
58
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Symlink resolution utilities for the probe package
3
+ * @module utils/symlink-utils
4
+ */
5
+
6
+ import fs from 'fs';
7
+ import { promises as fsPromises } from 'fs';
8
+
9
+ /**
10
+ * Get entry type following symlinks (async version)
11
+ *
12
+ * Uses fs.stat() which follows symlinks to get the actual target type.
13
+ * Falls back to dirent type if stat fails (e.g., broken symlink).
14
+ *
15
+ * @param {fs.Dirent} entry - Directory entry from readdir
16
+ * @param {string} fullPath - Full path to the entry
17
+ * @returns {Promise<{isFile: boolean, isDirectory: boolean, size: number}>}
18
+ */
19
+ export async function getEntryType(entry, fullPath) {
20
+ try {
21
+ const stats = await fsPromises.stat(fullPath);
22
+ return {
23
+ isFile: stats.isFile(),
24
+ isDirectory: stats.isDirectory(),
25
+ size: stats.size
26
+ };
27
+ } catch {
28
+ // Fall back to dirent type if stat fails (e.g., broken symlink)
29
+ return {
30
+ isFile: entry.isFile(),
31
+ isDirectory: entry.isDirectory(),
32
+ size: 0
33
+ };
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Get entry type following symlinks (sync version)
39
+ *
40
+ * Uses fs.statSync() which follows symlinks to get the actual target type.
41
+ * Falls back to dirent type if stat fails (e.g., broken symlink).
42
+ *
43
+ * @param {fs.Dirent} entry - Directory entry from readdir
44
+ * @param {string} fullPath - Full path to the entry
45
+ * @returns {{isFile: boolean, isDirectory: boolean, size: number}}
46
+ */
47
+ export function getEntryTypeSync(entry, fullPath) {
48
+ try {
49
+ const stats = fs.statSync(fullPath);
50
+ return {
51
+ isFile: stats.isFile(),
52
+ isDirectory: stats.isDirectory(),
53
+ size: stats.size
54
+ };
55
+ } catch {
56
+ // Fall back to dirent type if stat fails (e.g., broken symlink)
57
+ return {
58
+ isFile: entry.isFile(),
59
+ isDirectory: entry.isDirectory(),
60
+ size: 0
61
+ };
62
+ }
63
+ }