
Code Metrics Analysis
- 409 installs
- 305 repo stars
- Updated March 4, 2026
- aj-geddes/useful-ai-prompts
code-metrics-analysis is a code quality skill that measures complexity, churn, coverage gaps, and hotspot files for developers who need data-driven refactor priorities before merge or release.
About
code-metrics-analysis is a useful-ai-prompts skill that helps developers quantify code health before merge or release. It guides measurement of cyclomatic complexity, cognitive complexity, maintainability index, lines of code, function counts, and nesting depth using TypeScript AST analysis and Python radon tooling. The skill ships 4 reference guides covering a TypeScript complexity analyzer, Python radon metrics, an ESLint complexity plugin, and CI/CD quality gate patterns. Developers reach for code-metrics-analysis when assessing technical debt, identifying refactoring candidates, automating review gates, or tracking complexity trends across legacy modules. The skill emphasizes trends over absolute thresholds and combining multiple metrics rather than relying on a single score.
- Complexity and cyclomatic hotspot detection
- Churn and change-frequency risk flags
- Test coverage gap identification
- Dependency and coupling observations
- Actionable refactor prioritization lists
Code Metrics Analysis by the numbers
- 409 all-time installs (skills.sh)
- Ranked #258 of 1,352 Code Review & Quality skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/aj-geddes/useful-ai-prompts --skill code-metrics-analysisAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 409 |
|---|---|
| repo stars | ★ 305 |
| Last updated | March 4, 2026 |
| Repository | aj-geddes/useful-ai-prompts ↗ |
How do you find high-complexity refactor hotspots?
Evaluate complexity, churn, coverage gaps, and hotspot files before merge or release to catch maintainability risks and prioritize refactors.
Who is it for?
Engineering teams running pre-merge quality reviews or technical-debt audits on TypeScript and Python codebases.
Skip if: Developers who only need runtime profiling, load testing, or security penetration results without static complexity analysis.
When should I use this skill?
A developer asks to assess code complexity, identify refactoring candidates, monitor technical debt, or set CI quality gates from metrics.
What you get
Complexity metric reports, hotspot file rankings, CI quality gate thresholds, and prioritized refactor candidate lists with trend context.
- Complexity metric reports
- Hotspot rankings
- CI quality gate configs
By the numbers
- Bundles 4 reference guides for TypeScript, Python radon, ESLint complexity, and CI/CD quality gates
Files
Code Metrics Analysis
Table of Contents
Overview
Measure and analyze code quality metrics to identify complexity, maintainability issues, and areas for improvement.
When to Use
- Code quality assessment
- Identifying refactoring candidates
- Technical debt monitoring
- Code review automation
- CI/CD quality gates
- Team performance tracking
- Legacy code analysis
Quick Start
Minimal working example:
import * as ts from "typescript";
import * as fs from "fs";
interface ComplexityMetrics {
cyclomaticComplexity: number;
cognitiveComplexity: number;
linesOfCode: number;
functionCount: number;
classCount: number;
maxNestingDepth: number;
}
class CodeMetricsAnalyzer {
analyzeFile(filePath: string): ComplexityMetrics {
const sourceCode = fs.readFileSync(filePath, "utf-8");
const sourceFile = ts.createSourceFile(
filePath,
sourceCode,
ts.ScriptTarget.Latest,
true,
);
const metrics: ComplexityMetrics = {
cyclomaticComplexity: 0,
cognitiveComplexity: 0,
// ... (see reference guides for full implementation)Reference Guides
Detailed implementations in the references/ directory:
| Guide | Contents |
|---|---|
| TypeScript Complexity Analyzer | TypeScript Complexity Analyzer |
| Python Code Metrics (using radon) | Python Code Metrics (using radon) |
| ESLint Plugin for Complexity | ESLint Plugin for Complexity |
| CI/CD Quality Gates | CI/CD Quality Gates |
Best Practices
✅ DO
- Monitor metrics over time
- Set reasonable thresholds
- Focus on trends, not absolute numbers
- Automate metric collection
- Use metrics to guide refactoring
- Combine multiple metrics
- Include metrics in code reviews
❌ DON'T
- Use metrics as sole quality indicator
- Set unrealistic thresholds
- Ignore context and domain
- Punish developers for metrics
- Focus only on one metric
- Skip documentation
CI/CD Quality Gates
CI/CD Quality Gates
# .github/workflows/code-quality.yml
name: Code Quality
on: [pull_request]
jobs:
metrics:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: "18"
- name: Install dependencies
run: npm install
- name: Run complexity analysis
run: npx ts-node analyze-metrics.ts
- name: Check quality gates
run: |
COMPLEXITY=$(cat metrics.json | jq '.avgComplexity')
if (( $(echo "$COMPLEXITY > 10" | bc -l) )); then
echo "Average complexity too high: $COMPLEXITY"
exit 1
fi
- name: Upload metrics
uses: actions/upload-artifact@v2
with:
name: code-metrics
path: metrics.jsonESLint Plugin for Complexity
ESLint Plugin for Complexity
// eslint-plugin-complexity.js
module.exports = {
rules: {
"max-complexity": {
create(context) {
const maxComplexity = context.options[0] || 10;
let complexity = 0;
function increaseComplexity(node) {
complexity++;
}
function checkComplexity(node) {
if (complexity > maxComplexity) {
context.report({
node,
message: `Function has complexity of ${complexity}. Maximum allowed is ${maxComplexity}.`,
});
}
}
return {
FunctionDeclaration(node) {
complexity = 1;
},
"FunctionDeclaration:exit": checkComplexity,
IfStatement: increaseComplexity,
SwitchCase: increaseComplexity,
ForStatement: increaseComplexity,
WhileStatement: increaseComplexity,
DoWhileStatement: increaseComplexity,
ConditionalExpression: increaseComplexity,
LogicalExpression(node) {
if (node.operator === "&&" || node.operator === "||") {
increaseComplexity();
}
},
};
},
},
},
};Python Code Metrics (using radon)
Python Code Metrics (using radon)
from radon.complexity import cc_visit
from radon.metrics import mi_visit, h_visit
from radon.raw import analyze
import os
from typing import Dict, List
import json
class CodeMetricsAnalyzer:
def analyze_file(self, file_path: str) -> Dict:
"""Analyze a single Python file."""
with open(file_path, 'r') as f:
code = f.read()
# Cyclomatic complexity
complexity = cc_visit(code)
# Maintainability index
mi = mi_visit(code, True)
# Halstead metrics
halstead = h_visit(code)
# Raw metrics
raw = analyze(code)
return {
'file': file_path,
'complexity': [{
'name': block.name,
'complexity': block.complexity,
'lineno': block.lineno
} for block in complexity],
'maintainability_index': mi,
'halstead': {
'volume': halstead.total.volume if halstead.total else 0,
'difficulty': halstead.total.difficulty if halstead.total else 0,
'effort': halstead.total.effort if halstead.total else 0
},
'raw': {
'loc': raw.loc,
'lloc': raw.lloc,
'sloc': raw.sloc,
'comments': raw.comments,
'multi': raw.multi,
'blank': raw.blank
}
}
def analyze_project(self, directory: str) -> List[Dict]:
"""Analyze all Python files in a project."""
results = []
for root, dirs, files in os.walk(directory):
# Skip common directories
dirs[:] = [d for d in dirs if d not in ['.git', '__pycache__', 'venv', 'node_modules']]
for file in files:
if file.endswith('.py'):
file_path = os.path.join(root, file)
try:
result = self.analyze_file(file_path)
results.append(result)
except Exception as e:
print(f"Error analyzing {file_path}: {e}")
return results
def generate_report(self, results: List[Dict]) -> str:
"""Generate a markdown report."""
report = "# Code Metrics Report\n\n"
# Summary
total_files = len(results)
avg_mi = sum(r['maintainability_index'] for r in results) / total_files if total_files > 0 else 0
total_loc = sum(r['raw']['loc'] for r in results)
report += "## Summary\n\n"
report += f"- Total Files: {total_files}\n"
report += f"- Total LOC: {total_loc}\n"
report += f"- Average Maintainability Index: {avg_mi:.2f}\n\n"
# High complexity functions
report += "## High Complexity Functions\n\n"
high_complexity = []
for result in results:
for func in result['complexity']:
if func['complexity'] > 10:
high_complexity.append({
'file': result['file'],
**func
})
high_complexity.sort(key=lambda x: x['complexity'], reverse=True)
if not high_complexity:
report += "None found.\n\n"
else:
for func in high_complexity[:10]: # Top 10
report += f"- {func['file']}:{func['lineno']} - {func['name']}\n"
report += f" Complexity: {func['complexity']}\n\n"
# Low maintainability files
report += "## Low Maintainability Files\n\n"
low_mi = [r for r in results if r['maintainability_index'] < 65]
low_mi.sort(key=lambda x: x['maintainability_index'])
if not low_mi:
report += "None found.\n\n"
else:
for file in low_mi[:10]:
report += f"- {file['file']}\n"
report += f" MI: {file['maintainability_index']:.2f}\n"
report += f" LOC: {file['raw']['loc']}\n\n"
return report
def export_json(self, results: List[Dict], output_file: str):
"""Export results as JSON."""
with open(output_file, 'w') as f:
json.dump(results, f, indent=2)
# Usage
analyzer = CodeMetricsAnalyzer()
results = analyzer.analyze_project('./src')
report = analyzer.generate_report(results)
print(report)
# Export to JSON
analyzer.export_json(results, 'metrics.json')TypeScript Complexity Analyzer
TypeScript Complexity Analyzer
import * as ts from "typescript";
import * as fs from "fs";
interface ComplexityMetrics {
cyclomaticComplexity: number;
cognitiveComplexity: number;
linesOfCode: number;
functionCount: number;
classCount: number;
maxNestingDepth: number;
}
class CodeMetricsAnalyzer {
analyzeFile(filePath: string): ComplexityMetrics {
const sourceCode = fs.readFileSync(filePath, "utf-8");
const sourceFile = ts.createSourceFile(
filePath,
sourceCode,
ts.ScriptTarget.Latest,
true,
);
const metrics: ComplexityMetrics = {
cyclomaticComplexity: 0,
cognitiveComplexity: 0,
linesOfCode: sourceCode.split("\n").length,
functionCount: 0,
classCount: 0,
maxNestingDepth: 0,
};
this.visit(sourceFile, metrics);
return metrics;
}
private visit(
node: ts.Node,
metrics: ComplexityMetrics,
depth: number = 0,
): void {
metrics.maxNestingDepth = Math.max(metrics.maxNestingDepth, depth);
// Count functions
if (
ts.isFunctionDeclaration(node) ||
ts.isMethodDeclaration(node) ||
ts.isArrowFunction(node)
) {
metrics.functionCount++;
metrics.cyclomaticComplexity++;
}
// Count classes
if (ts.isClassDeclaration(node)) {
metrics.classCount++;
}
// Cyclomatic complexity contributors
if (
ts.isIfStatement(node) ||
ts.isConditionalExpression(node) ||
ts.isWhileStatement(node) ||
ts.isForStatement(node) ||
ts.isCaseClause(node)
) {
metrics.cyclomaticComplexity++;
}
// Cognitive complexity (simplified)
if (ts.isIfStatement(node)) {
metrics.cognitiveComplexity += 1 + depth;
}
if (ts.isWhileStatement(node) || ts.isForStatement(node)) {
metrics.cognitiveComplexity += 1 + depth;
}
// Recurse
const newDepth = this.increasesNesting(node) ? depth + 1 : depth;
ts.forEachChild(node, (child) => {
this.visit(child, metrics, newDepth);
});
}
private increasesNesting(node: ts.Node): boolean {
return (
ts.isIfStatement(node) ||
ts.isWhileStatement(node) ||
ts.isForStatement(node) ||
ts.isFunctionDeclaration(node) ||
ts.isMethodDeclaration(node)
);
}
calculateMaintainabilityIndex(metrics: ComplexityMetrics): number {
// Simplified maintainability index
const halsteadVolume = metrics.linesOfCode * 4.5; // Simplified
const cyclomaticComplexity = metrics.cyclomaticComplexity;
const linesOfCode = metrics.linesOfCode;
const mi = Math.max(
0,
((171 -
5.2 * Math.log(halsteadVolume) -
0.23 * cyclomaticComplexity -
16.2 * Math.log(linesOfCode)) *
100) /
171,
);
return Math.round(mi);
}
analyzeProject(directory: string): Record<string, ComplexityMetrics> {
const results: Record<string, ComplexityMetrics> = {};
const files = this.getTypeScriptFiles(directory);
for (const file of files) {
results[file] = this.analyzeFile(file);
}
return results;
}
private getTypeScriptFiles(dir: string): string[] {
const files: string[] = [];
const items = fs.readdirSync(dir);
for (const item of items) {
const fullPath = `${dir}/${item}`;
const stat = fs.statSync(fullPath);
if (
stat.isDirectory() &&
!item.startsWith(".") &&
item !== "node_modules"
) {
files.push(...this.getTypeScriptFiles(fullPath));
} else if (item.endsWith(".ts") && !item.endsWith(".d.ts")) {
files.push(fullPath);
}
}
return files;
}
generateReport(results: Record<string, ComplexityMetrics>): string {
let report = "# Code Metrics Report\n\n";
// Summary
const totalFiles = Object.keys(results).length;
const avgComplexity =
Object.values(results).reduce(
(sum, m) => sum + m.cyclomaticComplexity,
0,
) / totalFiles;
report += `## Summary\n\n`;
report += `- Total Files: ${totalFiles}\n`;
report += `- Average Complexity: ${avgComplexity.toFixed(2)}\n\n`;
// High complexity files
report += `## High Complexity Files\n\n`;
const highComplexity = Object.entries(results)
.filter(([_, m]) => m.cyclomaticComplexity > 10)
.sort((a, b) => b[1].cyclomaticComplexity - a[1].cyclomaticComplexity);
if (highComplexity.length === 0) {
report += "None found.\n\n";
} else {
for (const [file, metrics] of highComplexity) {
report += `- ${file}\n`;
report += ` - Cyclomatic: ${metrics.cyclomaticComplexity}\n`;
report += ` - Cognitive: ${metrics.cognitiveComplexity}\n`;
report += ` - LOC: ${metrics.linesOfCode}\n\n`;
}
}
return report;
}
}
// Usage
const analyzer = new CodeMetricsAnalyzer();
const results = analyzer.analyzeProject("./src");
const report = analyzer.generateReport(results);
console.log(report);#!/bin/bash
# scaffold-tests.sh - Generate test file scaffolding
# Usage: ./scaffold-tests.sh <source_file> [--framework jest|pytest|mocha]
set -euo pipefail
SOURCE_FILE="${{1:?Usage: $0 <source_file> [--framework jest|pytest|mocha]}}"
FRAMEWORK="${{2:-jest}}"
echo "Scaffolding tests for: $SOURCE_FILE (framework: $FRAMEWORK)"
# TODO: Implement test scaffolding logic
# - Parse source file for exported functions/classes
# - Generate test stubs for each export
# - Include setup/teardown boilerplate
# - Add common assertion patterns
echo "Test scaffolding complete."
// Test Template
// TODO: Customize for your testing framework and project
describe('ModuleName', () => {
// Setup
beforeEach(() => {
// TODO: Add test setup
});
afterEach(() => {
// TODO: Add cleanup
});
describe('functionName', () => {
it('should handle the happy path', () => {
// TODO: Add assertion
});
it('should handle edge cases', () => {
// TODO: Add edge case tests
});
it('should handle errors gracefully', () => {
// TODO: Add error handling tests
});
});
});
Related skills
How it compares
Pick code-metrics-analysis over generic lint skills when you need quantitative complexity, churn, and maintainability scoring to prioritize refactors.
FAQ
What metrics does code-metrics-analysis cover?
code-metrics-analysis covers cyclomatic complexity, cognitive complexity, maintainability index, lines of code, function and class counts, max nesting depth, and code churn. Reference guides include TypeScript AST analysis, Python radon, ESLint complexity rules, and CI gate patte
When should I run code-metrics-analysis?
code-metrics-analysis fits pre-merge review, release readiness checks, legacy audits, and CI quality gates. The skill recommends tracking trends over time and combining multiple metrics rather than blocking merges on one absolute complexity number.