FileScopeMCP
by admica
Verified
- FileScopeMCP
- src
import { getProjectRoot } from './global-state.js';
// File watching configuration
export interface FileWatchingConfig {
enabled: boolean; // Master switch for file watching
debounceMs: number; // Debounce time for file change events
ignoreDotFiles: boolean; // Whether to ignore files/dirs starting with a dot
autoRebuildTree: boolean; // Whether to auto-rebuild the tree on file changes
maxWatchedDirectories: number; // Limit to prevent watching too many directories
watchForNewFiles: boolean; // Watch for file additions
watchForDeleted: boolean; // Watch for file deletions
watchForChanged: boolean; // Watch for file modifications
}
// Configuration type for the application
export interface Config {
baseDirectory: string;
excludePatterns: string[];
fileWatching?: FileWatchingConfig;
version: string;
}
// Define concrete classes rather than just interfaces to ensure proper compilation
export class FileNode {
path: string = '';
name: string = '';
isDirectory: boolean = false;
children?: FileNode[];
dependencies?: string[]; // Outgoing dependencies (local files this file imports)
packageDependencies?: PackageDependency[]; // Outgoing dependencies (package files this file imports)
dependents?: string[]; // Incoming dependencies (files that import this file)
importance?: number; // 0-10 scale
summary?: string; // Human-readable summary of the file
mermaidDiagram?: MermaidDiagram; // Optional Mermaid diagram for this node
}
// New type for package dependencies with version information
export class PackageDependency {
name: string = ''; // Package name (e.g., 'react' or '@types/node')
version?: string; // Version if available (e.g., '^17.0.2')
path: string = ''; // Full resolved path
scope?: string; // Package scope (e.g., '@types' for '@types/node')
isDevDependency?: boolean; // Whether this is a dev dependency
// Helper to check for unresolved template literals
private static isUnresolvedTemplateLiteral(str: string): boolean {
return typeof str === 'string' &&
str.includes('${') &&
str.includes('}');
}
// Create a PackageDependency from a path string
static fromPath(path: string): PackageDependency {
const pkg = new PackageDependency();
pkg.path = path;
// Skip processing if the path contains an unresolved template literal
if (this.isUnresolvedTemplateLiteral(path)) {
return pkg; // Return empty package dependency without setting name
}
// Extract package name from path
if (path.includes('node_modules')) {
const parts = path.split('node_modules/');
if (parts.length > 1) {
const pkgPart = parts[1].split('/')[0];
// Skip setting name if pkgPart is a template literal
if (this.isUnresolvedTemplateLiteral(pkgPart)) {
return pkg;
}
// Handle scoped packages like @types/node
if (pkgPart.startsWith('@')) {
const scopedParts = parts[1].split('/');
if (scopedParts.length > 1) {
pkg.scope = scopedParts[0];
pkg.name = `${scopedParts[0]}/${scopedParts[1]}`;
}
} else {
pkg.name = pkgPart;
}
// For paths like node_modules/firebase/app, use firebase as the package name
if (parts[1].includes('/') && !pkgPart.startsWith('@')) {
const subparts = parts[1].split('/');
if (subparts.length > 1) {
pkg.name = subparts[0];
}
}
}
} else {
// For directly specified package imports
const parts = path.split('/');
if (parts.length > 0) {
const lastPart = parts[parts.length - 1];
if (path.includes('@supabase') || path.includes('@types') || path.includes('@firebase')) {
// Handle scoped package references in the path
for (const part of parts) {
if (part.startsWith('@')) {
const scopeName = part;
const nextIndex = parts.indexOf(part) + 1;
if (nextIndex < parts.length) {
const scopedPackage = parts[nextIndex];
pkg.scope = scopeName;
pkg.name = `${scopeName}/${scopedPackage}`;
break;
}
}
}
} else if (parts[0].startsWith('@')) {
// Scoped package
if (parts.length > 1) {
pkg.scope = parts[0];
pkg.name = `${parts[0]}/${parts[1]}`;
}
} else {
// Extract package name from the path
// Check for common package names in the path
const commonPkgs = ['react', 'axios', 'uuid', 'yup', 'express', 'firebase', 'date-fns'];
for (const commonPkg of commonPkgs) {
if (path.includes(`/${commonPkg}`) || path.includes(`\\${commonPkg}`)) {
pkg.name = commonPkg;
break;
}
}
// If no common package was found, use the last part of the path
if (!pkg.name && lastPart) {
// Avoid using lastPart if it's a template literal
if (!this.isUnresolvedTemplateLiteral(lastPart)) {
pkg.name = lastPart;
}
}
}
}
}
// Final sanity check on the name
if (this.isUnresolvedTemplateLiteral(pkg.name)) {
pkg.name = ''; // Clear the name if it's a template literal
}
return pkg;
}
}
export class SimpleFileNode {
path: string = '';
isDirectory: boolean = false;
children?: SimpleFileNode[];
}
export interface ToolResponse {
content: Array<
| { type: "text"; text: string; [key: string]: unknown }
| { type: "image"; mimeType: string; data: string; [key: string]: unknown }
| { type: "resource"; resource: { uri: string; text: string; mimeType?: string; [key: string]: unknown }; [key: string]: unknown }
>;
_meta?: { [key: string]: unknown };
isError?: boolean;
[key: string]: unknown;
}
export class FileTreeConfig {
filename: string = 'default-tree.json'; // Safe default filename
baseDirectory: string = getProjectRoot(); // Use project root as fallback
projectRoot: string = getProjectRoot(); // Always use project root
lastUpdated?: Date = new Date(); // Current time as default
}
export class FileTreeStorage {
config: FileTreeConfig = new FileTreeConfig();
fileTree: FileNode = new FileNode();
}
export class MermaidDiagramStyle {
nodeColors: {
highImportance: string; // Color for nodes with importance >= 8
mediumImportance: string; // Color for nodes with importance >= 5
lowImportance: string; // Color for nodes with importance < 5
package: string; // Color for package dependencies
packageScope: string; // Color for package scope groups
} = {
highImportance: '',
mediumImportance: '',
lowImportance: '',
package: '',
packageScope: ''
};
edgeColors: {
dependency: string; // Color for dependency relationships
directory: string; // Color for directory relationships
circular: string; // Color for circular dependencies
package: string; // Color for package dependency relationships
} = {
dependency: '',
directory: '',
circular: '',
package: ''
};
nodeShapes: {
file: string; // Shape for file nodes
directory: string; // Shape for directory nodes
important: string; // Shape for high-importance nodes
package: string; // Shape for package nodes
packageScope: string; // Shape for package scope nodes
} = {
file: '',
directory: '',
important: '',
package: '',
packageScope: ''
};
}
export class MermaidDiagramConfig {
style: 'default' | 'dependency' | 'directory' | 'hybrid' | 'package-deps' = 'default';
maxDepth?: number; // Maximum depth for directory trees
minImportance?: number; // Only show files above this importance (0-10)
showDependencies?: boolean; // Whether to show dependency relationships
showPackageDeps?: boolean; // Whether to show package dependencies
packageGrouping?: boolean; // Whether to group packages by scope
excludePackages?: string[]; // Packages to exclude from diagram
includeOnlyPackages?: string[]; // Only include these packages (if specified)
autoGroupThreshold?: number; // Auto-group nodes when parent has more than this many direct children (default: 8)
customStyle?: Partial<MermaidDiagramStyle>;
layout?: {
direction?: 'TB' | 'BT' | 'LR' | 'RL'; // Graph direction
rankSpacing?: number; // Space between ranks
nodeSpacing?: number; // Space between nodes
};
}
export class MermaidDiagramStats {
nodeCount: number = 0; // Total number of nodes in diagram
edgeCount: number = 0; // Total number of edges
maxDepth: number = 0; // Maximum depth in the tree
importantFiles: number = 0; // Number of files with importance >= minImportance
circularDeps: number = 0; // Number of circular dependencies
packageCount: number = 0; // Number of package dependencies
packageScopeCount: number = 0; // Number of package scopes
}
export class MermaidDiagram {
code: string = ''; // The Mermaid diagram code
style: MermaidDiagramStyle = new MermaidDiagramStyle();
stats: MermaidDiagramStats = new MermaidDiagramStats();
timestamp: Date = new Date(); // When the diagram was generated
}
export type GroupingRuleType = 'directory' | 'package' | 'dependency' | 'custom';
export interface GroupingRule {
type: GroupingRuleType;
condition: (nodes: FileNode[]) => boolean;
groupBy: (nodes: FileNode[]) => Map<string, FileNode[]>;
threshold: number;
description: string; // For debugging and logging
}
ID: mcrren8xsa