diff --git a/src/bin/matrixai-lint.ts b/src/bin/matrixai-lint.ts index e5c099a..421c8b2 100644 --- a/src/bin/matrixai-lint.ts +++ b/src/bin/matrixai-lint.ts @@ -96,7 +96,9 @@ function printDomainList( ): void { logger.info('Available lint domains:'); for (const domainInfo of domains) { - logger.info(`- ${domainInfo.domain}: ${domainInfo.description}`); + logger.info( + `Domain info: domain=${domainInfo.domain} description=${domainInfo.description}`, + ); } } @@ -106,24 +108,20 @@ function printExplain( ): void { logger.info('[matrixai-lint] Domain execution plan:'); for (const decision of decisions) { - logger.info(`[matrixai-lint] - domain: ${decision.domain}`); - logger.info( - `[matrixai-lint] selection: ${decision.selectionSource}${decision.explicitlyRequested ? ' (explicit)' : ''}`, - ); + logger.info(`[matrixai-lint] domain=${decision.domain}`); logger.info( - `[matrixai-lint] relevance: ${describeRelevance(decision)}`, + `[matrixai-lint] selection=${decision.selectionSource}${decision.explicitlyRequested ? ' (explicit)' : ''}`, ); + logger.info(`[matrixai-lint] relevance=${describeRelevance(decision)}`); logger.info( - `[matrixai-lint] availability: ${describeAvailability(decision)}`, + `[matrixai-lint] availability=${describeAvailability(decision)}`, ); if (decision.detectionError != null) { logger.error( - `[matrixai-lint] detection-error: ${decision.detectionError}`, + `[matrixai-lint] detection-error=${decision.detectionError}`, ); } - logger.info( - `[matrixai-lint] planned-action: ${decision.plannedAction}`, - ); + logger.info(`[matrixai-lint] planned-action=${decision.plannedAction}`); } } @@ -227,10 +225,10 @@ async function main(argv = process.argv) { } if (hadFailure) { - logger.error('[matrixai-lint] ✖ Linting failed.'); + logger.error('[matrixai-lint] ✖ Linting failed.'); process.exit(1); } else { - logger.info('[matrixai-lint] ✔ Linting passed.'); + logger.info('[matrixai-lint] ✔ Linting passed.'); } } diff --git a/src/domains/engine.ts b/src/domains/engine.ts index fd6d5de..19d9d1f 100644 --- a/src/domains/engine.ts +++ b/src/domains/engine.ts @@ -68,6 +68,12 @@ type LintDomainPlugin = { ) => LintDomainPluginResult); }; +function normalizeLogDetail(value: unknown): string { + return String(value) + .replace(/\r?\n+/g, ' | ') + .trim(); +} + function createLintDomainRegistry( plugins: readonly LintDomainPlugin[], ): Map { @@ -212,7 +218,7 @@ async function runLintDomainDecisions({ } if (plannedAction === 'fail-detection') { - const message = `[matrixai-lint] - Domain "${domain}" failed unexpectedly.\n${detectionError ?? 'Unknown detection error.'}`; + const message = `[matrixai-lint] - Domain "${domain}" failed unexpectedly. ${normalizeLogDetail(detectionError ?? 'Unknown detection error.')}`; logger.error(message); hadFailure = true; continue; @@ -222,7 +228,7 @@ async function runLintDomainDecisions({ if (explicitlyRequested) { const relevanceReason = detection?.relevanceReason ?? 'No files matched in effective scope.'; - const message = `[matrixai-lint] - Domain "${domain}" was explicitly requested, but no files matched. ${relevanceReason}`; + const message = `[matrixai-lint] - Domain "${domain}" was explicitly requested, but no files matched. ${relevanceReason}`; logger.warn(message); } continue; @@ -232,7 +238,7 @@ async function runLintDomainDecisions({ const unavailableReason = detection?.unavailableReason ?? `Tooling for domain "${domain}" is not available.`; - const message = `[matrixai-lint] - Domain "${domain}" cannot run. ${unavailableReason}`; + const message = `[matrixai-lint] - Domain "${domain}" cannot run. ${unavailableReason}`; logger.error(message); hadFailure = true; continue; @@ -242,7 +248,7 @@ async function runLintDomainDecisions({ const unavailableReason = detection?.unavailableReason ?? `Tooling for domain "${domain}" is not available.`; - const message = `[matrixai-lint] - Domain "${domain}" skipped. ${unavailableReason}`; + const message = `[matrixai-lint] - Domain "${domain}" skipped. ${unavailableReason}`; logger.warn(message); continue; } @@ -253,7 +259,7 @@ async function runLintDomainDecisions({ } if (detection == null) { - const message = `[matrixai-lint] - Domain "${domain}" is missing detection metadata.`; + const message = `[matrixai-lint] - Domain "${domain}" is missing detection metadata.`; logger.error(message); hadFailure = true; continue; @@ -265,7 +271,7 @@ async function runLintDomainDecisions({ hadFailure = true; } } catch (err) { - const message = `[matrixai-lint] - Domain "${domain}" failed unexpectedly.\n${String(err)}`; + const message = `[matrixai-lint] - Domain "${domain}" failed unexpectedly. ${normalizeLogDetail(err)}`; logger.error(message); hadFailure = true; } diff --git a/src/domains/eslint.ts b/src/domains/eslint.ts index b6bfefc..bd083e1 100644 --- a/src/domains/eslint.ts +++ b/src/domains/eslint.ts @@ -6,6 +6,12 @@ import { import * as utils from '../utils.js'; import { resolveLintConfig } from '../config.js'; +function normalizeLogDetail(value: unknown): string { + return String(value) + .replace(/\r?\n+/g, ' | ') + .trim(); +} + const ESLINT_FILE_EXTENSIONS = [ '.js', '.mjs', @@ -109,7 +115,12 @@ function createESLintDomainPlugin(): LintDomainPlugin { return { hadFailure: hadLintingErrors }; } catch (err) { - logger.error(`ESLint failed: \n${err}`); + const errorDetail = normalizeLogDetail(err); + logger.error( + errorDetail.length > 0 + ? `ESLint failed. ${errorDetail}` + : 'ESLint failed.', + ); return { hadFailure: true }; } }, diff --git a/src/domains/markdown.ts b/src/domains/markdown.ts index e97d2ce..52b6bff 100644 --- a/src/domains/markdown.ts +++ b/src/domains/markdown.ts @@ -17,6 +17,12 @@ const DEFAULT_MARKDOWN_SEARCH_ROOTS = [ './docs', ]; +function normalizeLogDetail(value: unknown): string { + return String(value) + .replace(/\r?\n+/g, ' | ') + .trim(); +} + function collectMarkdownFilesFromScope(patterns: readonly string[]): string[] { const matchedRelativeFiles = resolveFilesFromPatterns( patterns, @@ -92,7 +98,9 @@ function createMarkdownDomainPlugin({ try { if (prettierBin) { - logger.info(` ${prettierBin} \n ${prettierArgs.join('\n' + ' ')}`); + logger.info( + `Running prettier command: ${process.execPath} ${prettierBin} ${prettierArgs.join(' ')}`, + ); childProcess.execFileSync( process.execPath, [prettierBin, ...prettierArgs], @@ -104,7 +112,9 @@ function createMarkdownDomainPlugin({ }, ); } else { - logger.info('prettier ' + prettierArgs.join('\n' + ' ')); + logger.info( + `Running prettier command: prettier ${prettierArgs.join(' ')}`, + ); childProcess.execFileSync('prettier', prettierArgs, { stdio: 'inherit', windowsHide: true, @@ -114,10 +124,19 @@ function createMarkdownDomainPlugin({ }); } } catch (err) { + const errorDetail = normalizeLogDetail(err); if (!fix) { - logger.error('Prettier check failed.'); + logger.error( + errorDetail.length > 0 + ? `Prettier check failed. ${errorDetail}` + : 'Prettier check failed.', + ); } else { - logger.error('Prettier write failed. ' + err); + logger.error( + errorDetail.length > 0 + ? `Prettier write failed. ${errorDetail}` + : 'Prettier write failed.', + ); } return { hadFailure: true }; diff --git a/src/domains/shell.ts b/src/domains/shell.ts index 298e106..195ef2c 100644 --- a/src/domains/shell.ts +++ b/src/domains/shell.ts @@ -9,6 +9,12 @@ const platform = os.platform(); const SHELL_FILE_EXTENSIONS = ['.sh'] as const; +function normalizeLogDetail(value: unknown): string { + return String(value) + .replace(/\r?\n+/g, ' | ') + .trim(); +} + function resolveShellPatterns( shellPatterns: readonly string[] | undefined, defaultSearchRoots: readonly string[], @@ -55,7 +61,9 @@ function createShellDomainPlugin({ } logger.info('Running shellcheck:'); - logger.info(' ' + ['shellcheck', ...matchedFiles].join(' ')); + logger.info( + `Running shellcheck command: shellcheck ${matchedFiles.join(' ')}`, + ); try { childProcess.execFileSync('shellcheck', matchedFiles, { @@ -68,7 +76,12 @@ function createShellDomainPlugin({ return { hadFailure: false }; } catch (err) { - logger.error('Shellcheck failed. ' + err); + const errorDetail = normalizeLogDetail(err); + logger.error( + errorDetail.length > 0 + ? `Shellcheck failed. ${errorDetail}` + : 'Shellcheck failed.', + ); return { hadFailure: true }; } }, diff --git a/src/utils.ts b/src/utils.ts index a7ddaea..d13386d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -72,7 +72,9 @@ async function runESLint({ // PATH A - user supplied explicit globs if (explicitGlobs?.length) { logger.info('Linting with explicit patterns:'); - explicitGlobs.forEach((g) => logger.info(' ' + g)); + explicitGlobs.forEach((pattern) => { + logger.info(`Linting: ${pattern}`); + }); const eslint = new ESLint({ overrideConfigFile: configPath || defaultConfigPath, @@ -93,12 +95,14 @@ async function runESLint({ const { forceInclude, tsconfigPaths } = lintConfig.domains.eslint; if (tsconfigPaths.length === 0) { - logger.error('[matrixai-lint] ⚠ No tsconfig.json files found.'); + logger.error('[matrixai-lint] ⚠ No tsconfig.json files found.'); return true; } logger.info(`Found ${tsconfigPaths.length} tsconfig.json files:`); - tsconfigPaths.forEach((p) => logger.info(' ' + p)); + tsconfigPaths.forEach((tsconfigPath) => { + logger.info(`Using tsconfig: ${tsconfigPath}`); + }); const { files: patterns, ignore: ignorePats } = buildPatterns( tsconfigPaths, @@ -109,13 +113,15 @@ async function runESLint({ if (patterns.length === 0) { logger.warn( - '[matrixai-lint] ⚠ No ESLint targets were derived from configured tsconfig paths.', + '[matrixai-lint] ⚠ No ESLint targets were derived from configured tsconfig paths.', ); return false; } logger.info('Linting files:'); - patterns.forEach((p) => logger.info(' ' + p)); + patterns.forEach((pattern) => { + logger.info(`Linting: ${pattern}`); + }); const eslint = new ESLint({ overrideConfigFile: configPath || defaultConfigPath, @@ -144,9 +150,28 @@ async function lintAndReport( await ESLint.outputFixes(results); } + const errorCount = results.reduce( + (sum, result) => sum + result.errorCount, + 0, + ); + const warningCount = results.reduce( + (sum, result) => sum + result.warningCount, + 0, + ); + logger.info( + `ESLint summary: files=${results.length} errors=${errorCount} warnings=${warningCount} fix=${fix ? 'on' : 'off'}`, + ); + const formatter = await eslint.loadFormatter('stylish'); - logger.info(formatter.format(results)); - const hasErrors = results.some((r) => r.errorCount > 0); + const formattedOutput = await formatter.format(results); + for (const line of formattedOutput.split(/\r?\n/)) { + const normalizedLine = line.trim(); + if (normalizedLine.length > 0) { + logger.info(`ESLint detail: ${normalizedLine}`); + } + } + + const hasErrors = errorCount > 0; return hasErrors; } diff --git a/tests/bin/lint.test.ts b/tests/bin/lint.test.ts index 54d3a31..8ba4640 100644 --- a/tests/bin/lint.test.ts +++ b/tests/bin/lint.test.ts @@ -265,7 +265,7 @@ describe('matrixai-lint CLI domain semantics', () => { ); expect(stderrWriteSpy).toHaveBeenCalledWith( expect.stringContaining( - 'INFO:matrixai-lint:[matrixai-lint] - domain: markdown', + 'INFO:matrixai-lint:[matrixai-lint] domain=markdown', ), ); expect(stderrWriteSpy).toHaveBeenCalledWith(