When executing node bin/cx.js ignore show, the command correctly shows .md files in the exclusion patterns. However, when running node bin/cx.js -t, the .md files still appear in the tree output. This suggests a disconnect between how ignore patterns are stored and how they are applied during tree visualization.
After analyzing the codebase, we identified the following issues:
-
The tree command (
handleTreefunction inbin/cx.js) is correctly passing a special purpose parameter called'ignore-aware-tree'to thedirTreefunction:const tree = dirTree(absolutePath, 10, isVerbose, false, null, 'ignore-aware-tree');
-
In the
ExclusionManagerclass, there is special handling for the'ignore-aware-tree'purpose, but the extension pattern matching logic in thematchesUserDefinedPatternsmethod has issues with how it handles patterns like*.md. -
Specific areas requiring fixes:
- The
matchesUserDefinedPatternsmethod inlib/exclusionManager.jsdoes not consistently handle extension patterns for all file types - The pattern matching logic for extension-based exclusions like
*.mdis not accurately applied when the purpose is'ignore-aware-tree'
- The
-
Enhance the
matchesUserDefinedPatternsmethod to properly handle extension patterns:- Improve the extension pattern matching logic to be more reliable with patterns like
*.md - Ensure consistent handling across all file types
- Add more robust debugging for pattern matching to make issues easier to identify
- Improve the extension pattern matching logic to be more reliable with patterns like
-
Add specific tests for the
'ignore-aware-tree'purpose to ensure ignore patterns are respected:- Ensure extension patterns like
*.mdare correctly matched and applied - Verify that config-based patterns are given highest priority
- Ensure extension patterns like
-
Update the pattern matching logic for user-defined patterns to be more strict for the
'ignore-aware-tree'purpose
Update the matchesUserDefinedPatterns method in lib/exclusionManager.js to improve extension pattern handling:
matchesUserDefinedPatterns(filePath, purpose = 'content') {
const normalizedPath = path.normalize(filePath);
const relativePath = path.relative(this.basePath, normalizedPath);
const fileName = path.basename(filePath);
const fileExt = path.extname(filePath);
// Log current patterns for debugging
this.log(`Checking file ${filePath} against user patterns: ${[...this.configPatterns].join(', ')}`);
this.log(`File details: name=${fileName}, ext=${fileExt}, relativePath=${relativePath}`);
this.log(`Purpose: ${purpose}`);
// Check if we have any patterns at all
if (this.configPatterns.size === 0) {
this.log(`No user patterns to check against for ${filePath}`);
return false;
}
// For 'ignore-aware-tree' purpose, apply more strict pattern matching
if (purpose === 'ignore-aware-tree') {
// Improved direct extension matching
if (fileExt && fileExt.length > 0) {
const extWithoutDot = fileExt.substring(1); // Remove leading dot
const extensionPattern = `*.${extWithoutDot}`;
// Check if this extension pattern exists in configPatterns
if (this.configPatterns.has(extensionPattern)) {
this.log(`[ignore-aware-tree] Excluding ${filePath} due to extension pattern: ${extensionPattern}`);
return true;
}
// Also check by iterating through patterns for case-insensitive matching
for (const pattern of this.configPatterns) {
if (pattern.toLowerCase() === extensionPattern.toLowerCase()) {
this.log(`[ignore-aware-tree] Excluding ${filePath} due to case-insensitive extension pattern: ${pattern}`);
return true;
}
}
}
}
// Regular pattern matching (improved version)
for (const pattern of this.configPatterns) {
this.log(`Checking pattern: ${pattern} against file: ${filePath}`);
// For extension patterns like *.md, use more robust matching
if (pattern.startsWith('*.') && fileExt) {
const patternExt = pattern.substring(1); // *.md -> .md
if (fileExt.toLowerCase() === patternExt.toLowerCase()) {
this.log(`Extension match found: ${fileExt} matches pattern ${pattern}`);
return true;
}
}
// Use minimatch for other pattern types
const matchesRelative = minimatch(relativePath, pattern, { dot: true, matchBase: true });
const matchesFileName = minimatch(fileName, pattern, { dot: true, matchBase: true });
this.log(`Pattern ${pattern} match results: relativePath=${matchesRelative}, fileName=${matchesFileName}`);
if (matchesRelative || matchesFileName) {
this.log(`Excluding ${filePath} due to user pattern: ${pattern}`);
return true;
}
}
this.log(`No matches found for ${filePath} against user patterns`);
return false;
}Update the shouldExcludeFile method to ensure proper purpose handling:
shouldExcludeFile(filepath, purpose = 'content') {
if (!filepath) return true;
const cacheKey = this.getCacheKey(`file:${purpose}`, filepath);
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey);
}
// Extract file information
const fileName = path.basename(filepath);
const fileExt = path.extname(filepath);
// First, check if it matches user-defined patterns (highest priority)
// Pass purpose parameter to ensure correct context
if (this.matchesUserDefinedPatterns(filepath, purpose)) {
this.log(`Excluding file by user-defined pattern: ${filepath}`);
this.cache.set(cacheKey, true);
return true;
}
// Special handling for 'ignore-aware-tree' purpose
if (purpose === 'ignore-aware-tree') {
// For ignore-aware tree visualization, strictly respect all ignore patterns
if (this.matchesGitignoreOrConfig(filepath)) {
this.log(`Excluding file by gitignore/config for ignore-aware-tree: ${filepath}`);
this.cache.set(cacheKey, true);
return true;
}
}
// Rest of the method remains unchanged...
}Add additional diagnostic logging to help identify pattern matching issues:
// Add to ExclusionManager class
dumpPatterns() {
console.log('\nExclusion Patterns:');
console.log('Config patterns:', [...this.configPatterns].join(', '));
console.log('Gitignore patterns:', [...this.gitignorePatterns].join(', '));
console.log('System patterns:', [...this.systemPatterns].join(', '));
console.log('Negation patterns:', [...this.negationPatterns].join(', '));
console.log('All patterns:', [...this.patterns].join(', '));
}Then update the dirTree function in lib/directoryTree.js to use this diagnostic logging when in verbose mode:
function dirTree(filepath, maxDepth, verbose = false, debug = false, exclusionManager, purpose = 'tree') {
setVerbose(verbose);
try {
// Create exclusion manager if not provided
if (!exclusionManager) {
exclusionManager = new ExclusionManager(filepath, verbose);
if (verbose) {
console.log("Created new exclusion manager");
exclusionManager.dumpPatterns(); // Add diagnostic output
}
}
// Rest of the function remains unchanged...
}
}-
Test with a
.mdfile in the current directory:node bin/cx.js ignore show # Verify *.md is in ignore patterns node bin/cx.js -t # Verify .md files do not appear in tree node bin/cx.js -t -v # Run with verbose to see diagnostic output
-
Test with different file extensions:
node bin/cx.js ignore add "*.json" node bin/cx.js -t # Verify .json files do not appear in tree node bin/cx.js ignore clear # Clear all patterns node bin/cx.js -t # Verify .json files now appear in tree
-
Test with specific filenames:
node bin/cx.js ignore add "README.md" node bin/cx.js -t # Verify README.md does not appear, but other .md files do
The issue appears to be in how extension patterns like *.md are handled in the matchesUserDefinedPatterns method of the ExclusionManager class. By improving the pattern matching logic and adding more robust handling for the 'ignore-aware-tree' purpose, we can ensure that ignore patterns are correctly respected in the tree command output.