Home/
Part XIII — Expert Mode: Systems, Agents, and Automation/38. Building a Code-Change Agent Safely/38.4 Static analysis and lint gates
38.4 Static analysis and lint gates
Overview and links for this section of the guide.
Why Static Analysis
AI-generated code often has subtle issues that tests don't catch:
- Unused variables: Dead code that confuses readers
- Type errors: Wrong types that compile but fail at runtime
- Style violations: Inconsistent with codebase conventions
- Complexity: Overly complex functions that are hard to maintain
- Potential bugs: Patterns known to cause issues
Static analysis catches these before code review.
Lint Gates
// lint-gate.ts
interface LintConfig {
// Must pass - block merge if any fail
required: {
eslint: boolean;
typescript: boolean;
prettier: boolean;
};
// Nice to have - warn but don't block
recommended: {
complexity: number; // Max cyclomatic complexity
lineLength: number;
};
}
interface LintResult {
tool: string;
passed: boolean;
errors: LintError[];
warnings: LintWarning[];
fixable: number; // How many can be auto-fixed
}
export class LintGate {
async check(files: string[]): Promise {
const results = await Promise.all([
this.runESLint(files),
this.runTypeScript(files),
this.runPrettier(files)
]);
return results;
}
private async runESLint(files: string[]): Promise {
const eslint = new ESLint();
const results = await eslint.lintFiles(files);
const errors = results.flatMap(r =>
r.messages.filter(m => m.severity === 2)
);
const warnings = results.flatMap(r =>
r.messages.filter(m => m.severity === 1)
);
const fixable = results.reduce((sum, r) =>
sum + r.fixableErrorCount + r.fixableWarningCount, 0
);
return {
tool: 'eslint',
passed: errors.length === 0,
errors,
warnings,
fixable
};
}
private async runTypeScript(files: string[]): Promise {
const result = execSync(`npx tsc --noEmit ${files.join(' ')} 2>&1`, {
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe']
}).catch(e => e.stdout);
const errors = this.parseTypeScriptErrors(result);
return {
tool: 'typescript',
passed: errors.length === 0,
errors,
warnings: [],
fixable: 0
};
}
}
Implementation
// code-quality-pipeline.ts
export class CodeQualityPipeline {
async validate(diff: DiffBlock[]): Promise {
const changedFiles = diff.map(d => d.file);
// Run all checks in parallel
const [lint, typeCheck, complexity] = await Promise.all([
this.lintGate.check(changedFiles),
this.typeChecker.check(changedFiles),
this.complexityAnalyzer.analyze(changedFiles)
]);
// Aggregate results
const blocking = [
...lint.filter(r => !r.passed),
typeCheck.passed ? null : typeCheck
].filter(Boolean);
const warnings = [
...lint.flatMap(r => r.warnings),
...complexity.filter(c => c.score > this.config.recommended.complexity)
];
return {
canProceed: blocking.length === 0,
blocking,
warnings,
summary: this.generateSummary({ lint, typeCheck, complexity })
};
}
private generateSummary(results: any): string {
const lines = [];
if (results.lint.every(r => r.passed)) {
lines.push('✅ ESLint: All checks passed');
} else {
lines.push(`❌ ESLint: ${results.lint.reduce((s, r) => s + r.errors.length, 0)} errors`);
}
if (results.typeCheck.passed) {
lines.push('✅ TypeScript: No type errors');
} else {
lines.push(`❌ TypeScript: ${results.typeCheck.errors.length} errors`);
}
return lines.join('\n');
}
}
Auto-Fixing Lint Errors
// auto-fixer.ts
export class LintAutoFixer {
async fixAndRecheck(files: string[]): Promise {
// Run ESLint with --fix
await this.runWithFix('eslint', files);
// Run Prettier
await this.runWithFix('prettier', files);
// Re-check to see what's left
const remaining = await this.lintGate.check(files);
const stillFailing = remaining.filter(r => !r.passed);
if (stillFailing.length > 0) {
// Ask the agent to fix non-auto-fixable issues
return {
autoFixed: true,
remainingErrors: stillFailing.flatMap(r => r.errors),
needsManualFix: true
};
}
return {
autoFixed: true,
remainingErrors: [],
needsManualFix: false
};
}
async agentFix(errors: LintError[], code: string): Promise {
const prompt = `Fix these lint errors in the code:
Errors:
${errors.map(e => `- Line ${e.line}: ${e.message} (${e.ruleId})`).join('\n')}
Code:
\`\`\`
${code}
\`\`\`
Return the fixed code.`;
const response = await this.model.generateContent({
contents: [{ role: 'user', parts: [{ text: prompt }] }]
});
return this.extractCode(response.response.text());
}
}
Strict Mode for AI Code
Consider stricter lint rules for AI-generated code than for human code. AI tends to produce subtly wrong patterns that stricter rules catch.