diff --git a/linters/typescript/src/cli.ts b/linters/typescript/src/cli.ts index 89f81ca..a16b229 100644 --- a/linters/typescript/src/cli.ts +++ b/linters/typescript/src/cli.ts @@ -19,30 +19,63 @@ function parseLogLevel(logLevelArg: string): LogLevel { } } +function printUsage() { + console.log(` +Usage: codebase-context-lint [options] + +Codebase Context Linter +This tool validates context files (.context.md, .context.yaml, .context.json), +.contextdocs.md, and .contextignore according to the Codebase Context Specification. + +Arguments: + The directory containing the files to lint + +Options: + --log-level Set the logging level (error, warn, info, debug) + Default: info + --help, -h Show this help message + +Examples: + codebase-context-lint . + codebase-context-lint /path/to/project --log-level debug + +For more information, visit: https://github.com/Agentic-Insights/codebase-context-spec +`); +} + async function main() { const args = process.argv.slice(2); let directoryToLint: string | undefined; let logLevel = LogLevel.INFO; for (let i = 0; i < args.length; i++) { - if (args[i] === '--log-level' && i + 1 < args.length) { - logLevel = parseLogLevel(args[i + 1]); - i++; // Skip the next argument as it's the log level value - } else if (!directoryToLint) { - directoryToLint = args[i]; + switch (args[i]) { + case '--log-level': + if (i + 1 < args.length) { + logLevel = parseLogLevel(args[++i]); + } else { + console.error('Error: --log-level requires a value'); + process.exit(1); + } + break; + case '--help': + case '-h': + printUsage(); + process.exit(0); + default: + if (!directoryToLint) { + directoryToLint = args[i]; + } else { + console.error(`Error: Unexpected argument '${args[i]}'`); + printUsage(); + process.exit(1); + } } } if (!directoryToLint) { - console.error(` -Usage: codebase-context-lint [--log-level ] - -Codebase Context Linter -This tool validates context files, including .contextdocs.md and .contextignore, according to the Codebase Context Specification. - -Options: - --log-level Set the logging level (error, warn, info, debug). Default: info -`); + console.error('Error: Directory to lint is required'); + printUsage(); process.exit(1); } diff --git a/linters/typescript/src/context_linter.ts b/linters/typescript/src/context_linter.ts index 68d57a6..723e24b 100644 --- a/linters/typescript/src/context_linter.ts +++ b/linters/typescript/src/context_linter.ts @@ -77,8 +77,8 @@ export class ContextLinter { isValid = await this.handleContextdocs(directoryPath) && isValid; isValid = await this.handleContextFilesRecursively(directoryPath) && isValid; - // Log ignored files - this.logIgnoredFiles(directoryPath); + // Report on .contextignore usage + this.reportContextignoreUsage(directoryPath); // Clear all caches after processing the directory this.clearAllCaches(); @@ -109,6 +109,9 @@ export class ContextLinter { if (await fileExists(contextignorePath)) { const content = await fs.promises.readFile(contextignorePath, 'utf-8'); await this.contextignoreLinter.lintContextignoreFile(content, contextignorePath); + this.log(LogLevel.INFO, `Found .contextignore file at ${this.normalizePath(contextignorePath)}`); + } else { + this.log(LogLevel.INFO, 'No .contextignore file found. All files will be processed.'); } } @@ -174,19 +177,33 @@ export class ContextLinter { } /** - * Log ignored files in the directory + * Report on .contextignore usage * @param directoryPath The path of the directory to check for ignored files */ - private logIgnoredFiles(directoryPath: string): void { + private reportContextignoreUsage(directoryPath: string): void { + const ignoredFiles = this.contextignoreLinter.getIgnoredFiles(directoryPath); + const ignoredDirectories = this.contextignoreLinter.getIgnoredDirectories(directoryPath); + + this.log(LogLevel.INFO, '\n.contextignore Usage Report:'); + this.log(LogLevel.INFO, `Total ignored files: ${ignoredFiles.length}`); + this.log(LogLevel.INFO, `Total ignored directories: ${ignoredDirectories.length}`); + if (this.logLevel === LogLevel.DEBUG) { - const ignoredFiles = this.contextignoreLinter.getIgnoredFiles(directoryPath); if (ignoredFiles.length > 0) { this.log(LogLevel.DEBUG, '\nIgnored files:'); for (const file of ignoredFiles) { this.log(LogLevel.DEBUG, ` ${this.normalizePath(file)}`); } } + if (ignoredDirectories.length > 0) { + this.log(LogLevel.DEBUG, '\nIgnored directories:'); + for (const dir of ignoredDirectories) { + this.log(LogLevel.DEBUG, ` ${this.normalizePath(dir)}`); + } + } } + + this.log(LogLevel.INFO, ''); // Add a blank line for better readability } /** diff --git a/linters/typescript/src/contextignore_linter.ts b/linters/typescript/src/contextignore_linter.ts index 6c2932f..1135391 100644 --- a/linters/typescript/src/contextignore_linter.ts +++ b/linters/typescript/src/contextignore_linter.ts @@ -224,4 +224,40 @@ export class ContextignoreLinter { return []; } } + + /** + * Get a list of ignored directories in a directory + * @param directoryPath The path of the directory to check + * @returns An array of ignored directory paths + */ + public getIgnoredDirectories(directoryPath: string): string[] { + try { + const ig = this.ignoreCache.get(directoryPath); + if (!ig) { + return []; + } + + const ignoredDirectories: string[] = []; + const walk = (dir: string) => { + const dirents = fs.readdirSync(dir, { withFileTypes: true }); + for (const dirent of dirents) { + if (dirent.isDirectory()) { + const res = path.join(dir, dirent.name); + const relativePath = path.relative(directoryPath, res); + if (ig.ignores(relativePath)) { + ignoredDirectories.push(res); + } else { + walk(res); + } + } + } + }; + + walk(directoryPath); + return ignoredDirectories; + } catch (error) { + this.log(LogLevel.ERROR, `Error getting ignored directories for directory ${directoryPath}: ${error instanceof Error ? error.message : String(error)}`); + return []; + } + } } \ No newline at end of file