Skip to content
This repository was archived by the owner on Oct 10, 2025. It is now read-only.

Commit 5d44aa5

Browse files
committed
docs: fix logging
1 parent 25c4ad0 commit 5d44aa5

File tree

7 files changed

+231
-166
lines changed

7 files changed

+231
-166
lines changed

linters/.context.md

Lines changed: 0 additions & 76 deletions
This file was deleted.

linters/typescript/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

linters/typescript/src/cli.ts

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,56 @@
11
#!/usr/bin/env node
22

3-
import { ContextLinter } from './context_linter';
3+
import { ContextLinter, LogLevel } from './context_linter';
44
import { getPackageVersion } from './utils';
55

6+
function parseLogLevel(logLevelArg: string): LogLevel {
7+
switch (logLevelArg.toLowerCase()) {
8+
case 'error':
9+
return LogLevel.ERROR;
10+
case 'warn':
11+
return LogLevel.WARN;
12+
case 'info':
13+
return LogLevel.INFO;
14+
case 'debug':
15+
return LogLevel.DEBUG;
16+
default:
17+
console.warn(`Invalid log level: ${logLevelArg}. Using default (INFO).`);
18+
return LogLevel.INFO;
19+
}
20+
}
21+
622
async function main() {
723
const args = process.argv.slice(2);
8-
if (args.length !== 1) {
24+
let directoryToLint: string | undefined;
25+
let logLevel = LogLevel.INFO;
26+
27+
for (let i = 0; i < args.length; i++) {
28+
if (args[i] === '--log-level' && i + 1 < args.length) {
29+
logLevel = parseLogLevel(args[i + 1]);
30+
i++; // Skip the next argument as it's the log level value
31+
} else if (!directoryToLint) {
32+
directoryToLint = args[i];
33+
}
34+
}
35+
36+
if (!directoryToLint) {
937
console.error(`
10-
Usage: codebase-context-lint <directory_to_lint>
38+
Usage: codebase-context-lint <directory_to_lint> [--log-level <level>]
1139
1240
Codebase Context Linter
1341
This tool validates context files, including .contextdocs.md and .contextignore, according to the Codebase Context Specification.
42+
43+
Options:
44+
--log-level <level> Set the logging level (error, warn, info, debug). Default: info
1445
`);
1546
process.exit(1);
1647
}
1748

18-
const directoryToLint = args[0];
1949
const packageVersion = await getPackageVersion();
20-
const linter = new ContextLinter();
21-
await linter.lintDirectory(directoryToLint, packageVersion);
50+
const linter = new ContextLinter(logLevel);
51+
const isValid = await linter.lintDirectory(directoryToLint, packageVersion);
52+
53+
process.exit(isValid ? 0 : 1);
2254
}
2355

2456
main().catch(error => {

linters/typescript/src/context_linter.ts

Lines changed: 60 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ import { ContextignoreLinter } from './contextignore_linter';
88
import { getContextFiles, lintFileIfExists, fileExists, printHeader } from './utils/file_utils';
99
import { ContextValidator, ValidationResult, SectionValidationResult } from './utils/validator';
1010

11+
export enum LogLevel {
12+
ERROR,
13+
WARN,
14+
INFO,
15+
DEBUG
16+
}
17+
1118
/**
1219
* ContextLinter class handles the linting of .context files (md, yaml, json)
1320
* and coordinates the use of ContextignoreLinter and ContextdocsLinter.
@@ -17,12 +24,31 @@ export class ContextLinter {
1724
private contextdocsLinter: ContextdocsLinter;
1825
private contextignoreLinter: ContextignoreLinter;
1926
private contextValidator: ContextValidator;
27+
private logLevel: LogLevel;
2028

21-
constructor() {
29+
constructor(logLevel: LogLevel = LogLevel.INFO) {
2230
this.md = new MarkdownIt();
23-
this.contextdocsLinter = new ContextdocsLinter();
24-
this.contextignoreLinter = new ContextignoreLinter();
25-
this.contextValidator = new ContextValidator();
31+
this.contextdocsLinter = new ContextdocsLinter(logLevel);
32+
this.contextignoreLinter = new ContextignoreLinter(logLevel);
33+
this.contextValidator = new ContextValidator(logLevel);
34+
this.logLevel = logLevel;
35+
}
36+
37+
private log(level: LogLevel, message: string): void {
38+
if (level <= this.logLevel) {
39+
switch (level) {
40+
case LogLevel.ERROR:
41+
console.error(message);
42+
break;
43+
case LogLevel.WARN:
44+
console.warn(message);
45+
break;
46+
case LogLevel.INFO:
47+
case LogLevel.DEBUG:
48+
console.log(message);
49+
break;
50+
}
51+
}
2652
}
2753

2854
/**
@@ -54,18 +80,26 @@ export class ContextLinter {
5480
// Log ignored files
5581
this.logIgnoredFiles(directoryPath);
5682

57-
// Clear ignore cache after processing the directory
58-
this.contextignoreLinter.clearCache();
83+
// Clear all caches after processing the directory
84+
this.clearAllCaches();
5985

60-
console.log('Linting completed.');
86+
this.log(LogLevel.INFO, 'Linting completed.');
6187

6288
return isValid;
6389
} catch (error) {
64-
console.error(`Error linting directory: ${error instanceof Error ? error.message : String(error)}`);
90+
this.log(LogLevel.ERROR, `Error linting directory: ${error instanceof Error ? error.message : String(error)}`);
6591
return false;
6692
}
6793
}
6894

95+
/**
96+
* Clear all caches from linter components
97+
*/
98+
private clearAllCaches(): void {
99+
this.contextignoreLinter.clearCache();
100+
this.contextValidator.clearCache();
101+
}
102+
69103
/**
70104
* Initialize ignore patterns from .contextignore file
71105
* @param directoryPath The path of the directory containing .contextignore
@@ -120,7 +154,7 @@ export class ContextLinter {
120154
result = await this.lintJsonFile(fileContent, fullPath);
121155
break;
122156
default:
123-
console.warn(`Unsupported file extension: ${fileExtension}`);
157+
this.log(LogLevel.WARN, `Unsupported file extension: ${fileExtension}`);
124158
continue;
125159
}
126160

@@ -148,9 +182,9 @@ export class ContextLinter {
148182
private logIgnoredFiles(directoryPath: string): void {
149183
const ignoredFiles = this.contextignoreLinter.getIgnoredFiles(directoryPath);
150184
if (ignoredFiles.length > 0) {
151-
console.log('\nIgnored files:');
185+
this.log(LogLevel.INFO, '\nIgnored files:');
152186
for (const file of ignoredFiles) {
153-
console.log(` ${this.normalizePath(file)}`);
187+
this.log(LogLevel.INFO, ` ${this.normalizePath(file)}`);
154188
}
155189
}
156190
}
@@ -172,7 +206,7 @@ export class ContextLinter {
172206
isValid: validationResult.isValid && markdownValid
173207
};
174208
} catch (error) {
175-
console.error(` Error parsing Markdown file: ${error}`);
209+
this.log(LogLevel.ERROR, ` Error parsing Markdown file: ${error}`);
176210
return {
177211
isValid: false,
178212
coveragePercentage: 0,
@@ -202,19 +236,19 @@ export class ContextLinter {
202236
if (token.type === 'link_open') {
203237
const hrefToken = tokens[tokens.indexOf(token) + 1];
204238
if (hrefToken.type !== 'text' || !hrefToken.content.startsWith('http')) {
205-
console.error(' Warning: Link may be improperly formatted or using relative path.');
239+
this.log(LogLevel.WARN, ' Warning: Link may be improperly formatted or using relative path.');
206240
isValid = false;
207241
}
208242
}
209243

210244
if (token.type === 'fence' && !token.info) {
211-
console.error(' Warning: Code block is missing language specification.');
245+
this.log(LogLevel.WARN, ' Warning: Code block is missing language specification.');
212246
isValid = false;
213247
}
214248
}
215249

216250
if (!hasTitle) {
217-
console.error(' Error: Markdown content should start with a title (H1 heading).');
251+
this.log(LogLevel.ERROR, ' Error: Markdown content should start with a title (H1 heading).');
218252
isValid = false;
219253
}
220254

@@ -228,16 +262,16 @@ export class ContextLinter {
228262
* @returns A ValidationResult object
229263
*/
230264
private async lintYamlFile(content: string, filePath: string): Promise<ValidationResult> {
231-
console.log(' - Validating YAML structure');
265+
this.log(LogLevel.INFO, ' - Validating YAML structure');
232266

233267
try {
234268
const yamlData = this.parseYaml(content);
235269
return this.contextValidator.validateContextData(yamlData, 'yaml');
236270
} catch (error) {
237271
if (error instanceof yaml.YAMLException) {
238-
console.error(` Error parsing YAML file: ${this.formatYamlError(error)}`);
272+
this.log(LogLevel.ERROR, ` Error parsing YAML file: ${this.formatYamlError(error)}`);
239273
} else {
240-
console.error(` Error parsing YAML file: ${error}`);
274+
this.log(LogLevel.ERROR, ` Error parsing YAML file: ${error}`);
241275
}
242276
return {
243277
isValid: false,
@@ -257,16 +291,16 @@ export class ContextLinter {
257291
* @returns A ValidationResult object
258292
*/
259293
private async lintJsonFile(content: string, filePath: string): Promise<ValidationResult> {
260-
console.log(' - Validating JSON structure');
294+
this.log(LogLevel.INFO, ' - Validating JSON structure');
261295

262296
try {
263297
const jsonData = JSON.parse(content) as Record<string, unknown>;
264298
return this.contextValidator.validateContextData(jsonData, 'json');
265299
} catch (error) {
266300
if (error instanceof SyntaxError) {
267-
console.error(` Error parsing JSON file: ${this.formatJsonError(error, content)}`);
301+
this.log(LogLevel.ERROR, ` Error parsing JSON file: ${this.formatJsonError(error, content)}`);
268302
} else {
269-
console.error(` Error parsing JSON file: ${error}`);
303+
this.log(LogLevel.ERROR, ` Error parsing JSON file: ${error}`);
270304
}
271305
return {
272306
isValid: false,
@@ -286,19 +320,19 @@ export class ContextLinter {
286320
*/
287321
private printValidationResult(result: ValidationResult, filePath: string): void {
288322
const relativePath = this.normalizePath(path.relative(process.cwd(), filePath));
289-
console.log(`Linting file: ${relativePath}`);
323+
this.log(LogLevel.INFO, `Linting file: ${relativePath}`);
290324

291-
console.log(`main context: ${result.coveragePercentage.toFixed(2)}% (${result.coveredFields}/${result.totalFields} top level fields)`);
325+
this.log(LogLevel.INFO, `main context: ${result.coveragePercentage.toFixed(2)}% (${result.coveredFields}/${result.totalFields} top level fields)`);
292326

293327
for (const [sectionName, sectionResult] of Object.entries(result.sections)) {
294-
console.log(`|- ${sectionName}: ${sectionResult.coveragePercentage.toFixed(2)}% (${sectionResult.coveredFields}/${sectionResult.totalFields} fields)`);
328+
this.log(LogLevel.INFO, `|- ${sectionName}: ${sectionResult.coveragePercentage.toFixed(2)}% (${sectionResult.coveredFields}/${sectionResult.totalFields} fields)`);
295329
}
296330

297331
if (!result.isValid) {
298-
console.warn(`⚠️ File has coverage warnings`);
332+
this.log(LogLevel.WARN, `⚠️ File has coverage warnings`);
299333
}
300334

301-
console.log(''); // Add a blank line for better readability
335+
this.log(LogLevel.INFO, ''); // Add a blank line for better readability
302336
}
303337

304338
private parseYaml(content: string): Record<string, unknown> {

0 commit comments

Comments
 (0)