Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,93 @@ export class JSONModelsProvider extends ModelsProviderConnector {
return this.isValidSingleModel(data);
}

/**
* Determines whether a file path should be ignored by the directory watcher.
*
* This method implements a sophisticated filtering strategy for dot-segment paths
* (paths containing directories that start with a dot, like .git, .env, .cache).
*
* **Filtering Strategy:**
* 1. Paths WITHOUT dot segments: Never ignored
* 2. Paths WITH dot segments:
* - If SMYTH_PATH is not configured: All ignored
* - If SMYTH_PATH is configured:
* - Allow the watched directory even if SMYTH_PATH contains dot-segments
* (e.g., /home/user/.smyth/models/OpenAI/default.json is allowed)
* - Ignore dot-segments INSIDE the models directory
* (e.g., /home/user/.smyth/models/.hidden/model.json is ignored)
* - Paths outside watched directory: Ignored
*
* @param filePath - The file path to check
* @param watchedDir - The absolute path of the directory being watched (models folder)
* @param smythPath - The resolved SMYTH_PATH, or null if not configured
* @returns true if the path should be ignored, false if it should be watched
*
* @example
* ```typescript
* // Path without dot-segment (allowed)
* shouldIgnorePath('/models/OpenAI/default.json', '/models', '/home/.smyth') // => false
*
* // Dot-segment inside models directory (ignored)
* shouldIgnorePath('/models/.git/config', '/models', '/home/.smyth') // => true
*
* // Dot-segment in parent path only (allowed)
* shouldIgnorePath('/home/.smyth/models/OpenAI/default.json', '/home/.smyth/models', '/home/.smyth') // => false
* ```
*/
private shouldIgnorePath(filePath: string, watchedDir: string, smythPath: string | null): boolean {
// Check if the file path contains a dot-segment (e.g., /.git/, /.env/, /.cache/)
// Regex explanation: [\\/]\. matches a path separator (/ or \) followed by a dot
const hasDotSegment = /[\\/]\./.test(filePath);

// CASE 1: If there is NO dot-segment at all, we never ignore this path
// Examples: /models/OpenAI/default.json, /models/Anthropic/claude.json
if (!hasDotSegment) {
return false;
}

// CASE 2: If there IS a dot-segment and SMYTH_PATH is not configured,
// we ignore all such paths to prevent watching system/hidden files
// Examples: /.git/config, /node_modules/.cache/file.json
if (hasDotSegment && !smythPath) {
return true;
}

// Resolve the file path to an absolute path for accurate comparison
// This ensures we can reliably compare against the watched directory path
const resolvedPath = path.resolve(filePath);

// Check if the resolved path is inside the watched directory (models folder)
// This handles two cases:
// 1. The path exactly matches the watched directory
// 2. The path is a child of the watched directory (starts with watchedDir + separator)
const isInsideWatchedDir = resolvedPath === watchedDir || resolvedPath.startsWith(watchedDir + path.sep);

// CASE 3: If the path is outside the watched directory, ignore it
// This prevents watching unrelated files that happen to have dot-segments
if (!isInsideWatchedDir) {
return true;
}

// CASE 4: Path is inside the watched directory
// Now we need to determine if the dot-segment is in the models directory itself
// or if it is part of the parent path (e.g., SMYTH_PATH containing .smyth)

// Get the relative path from the watched directory to determine where the dot-segment is
const relativePath = path.relative(watchedDir, resolvedPath);

// Check if the dot-segment appears in the relative portion (inside models directory)
// Regex explanation: (^|[\\/])\. matches a dot at the start OR after a path separator
// Examples that match: '.git/config', 'subdir/.hidden/file.json'
const hasDotSegmentInsideWatchedDir = /(^|[\\/])\./.test(relativePath);

// FINAL DECISION:
// - If dot-segment is INSIDE the models directory (e.g., models/.git/config), IGNORE it (return true)
// - If dot-segment is OUTSIDE the models directory (e.g., /home/user/.smyth/models/OpenAI/default.json), ALLOW it (return false)
// This allows SMYTH_PATH to contain dot-segments while preventing dot-segments within the models folder
return hasDotSegmentInsideWatchedDir;
}

private initDirWatcher(dir) {
const stats = fsSync.statSync(dir);

Expand Down Expand Up @@ -257,8 +344,12 @@ export class JSONModelsProvider extends ModelsProviderConnector {
maxWait: 5000,
});

const smythPath = process.env.SMYTH_PATH ? path.resolve(process.env.SMYTH_PATH) : null;
const watchedDir = path.resolve(dir);

const watcher = chokidar.watch(dir, {
ignored: /(^|[\/\\])\../, // ignore dotfiles
// Use the extracted method for path filtering
ignored: (filePath: string) => this.shouldIgnorePath(filePath, watchedDir, smythPath),
persistent: true,
ignoreInitial: true, // Don't fire events for files that already exist
awaitWriteFinish: {
Expand Down