Back to Skills

technical-debt-assessment

aj-geddes
Updated Today
20 views
7
7
View on GitHub
Developmentgeneral

About

This skill helps developers systematically identify, measure, and prioritize technical debt in a codebase using code analysis and metrics. It is designed for use when planning refactoring, evaluating legacy systems, or making architectural decisions. The tool enables informed prioritization by assessing the effort, impact, and ongoing "interest" of different debt items.

Documentation

Technical Debt Assessment

Overview

Systematically identify, measure, and manage technical debt to make informed decisions about code quality investments.

When to Use

  • Legacy code evaluation
  • Refactoring prioritization
  • Sprint planning
  • Code quality initiatives
  • Acquisition due diligence
  • Architectural decisions

Implementation Examples

1. Technical Debt Calculator

interface DebtItem {
  id: string;
  title: string;
  description: string;
  category: 'code' | 'architecture' | 'test' | 'documentation' | 'security';
  severity: 'low' | 'medium' | 'high' | 'critical';
  effort: number; // hours
  impact: number; // 1-10 scale
  interest: number; // cost per sprint if not fixed
}

class TechnicalDebtAssessment {
  private items: DebtItem[] = [];

  addDebtItem(item: DebtItem): void {
    this.items.push(item);
  }

  calculatePriority(item: DebtItem): number {
    const severityWeight = {
      low: 1,
      medium: 2,
      high: 3,
      critical: 4
    };

    const priority =
      (item.impact * 10 + item.interest * 5 + severityWeight[item.severity] * 3) /
      (item.effort + 1);

    return priority;
  }

  getPrioritizedList(): Array<DebtItem & { priority: number }> {
    return this.items
      .map(item => ({
        ...item,
        priority: this.calculatePriority(item)
      }))
      .sort((a, b) => b.priority - a.priority);
  }

  getDebtByCategory(): Record<string, DebtItem[]> {
    return this.items.reduce((acc, item) => {
      acc[item.category] = acc[item.category] || [];
      acc[item.category].push(item);
      return acc;
    }, {} as Record<string, DebtItem[]>);
  }

  getTotalEffort(): number {
    return this.items.reduce((sum, item) => sum + item.effort, 0);
  }

  getTotalInterest(): number {
    return this.items.reduce((sum, item) => sum + item.interest, 0);
  }

  generateReport(): string {
    const prioritized = this.getPrioritizedList();
    const byCategory = this.getDebtByCategory();

    let report = '# Technical Debt Assessment\n\n';

    // Summary
    report += '## Summary\n\n';
    report += `- Total Items: ${this.items.length}\n`;
    report += `- Total Effort: ${this.getTotalEffort()} hours\n`;
    report += `- Monthly Interest: ${this.getTotalInterest()} hours\n\n`;

    // By Category
    report += '## By Category\n\n';
    for (const [category, items] of Object.entries(byCategory)) {
      const effort = items.reduce((sum, item) => sum + item.effort, 0);
      report += `- ${category}: ${items.length} items (${effort} hours)\n`;
    }
    report += '\n';

    // Top Priority Items
    report += '## Top Priority Items\n\n';
    for (const item of prioritized.slice(0, 10)) {
      report += `### ${item.title} (Priority: ${item.priority.toFixed(2)})\n`;
      report += `- Category: ${item.category}\n`;
      report += `- Severity: ${item.severity}\n`;
      report += `- Effort: ${item.effort} hours\n`;
      report += `- Impact: ${item.impact}/10\n`;
      report += `- Interest: ${item.interest} hours/sprint\n`;
      report += `\n${item.description}\n\n`;
    }

    return report;
  }
}

// Usage
const assessment = new TechnicalDebtAssessment();

assessment.addDebtItem({
  id: 'debt-1',
  title: 'Legacy API endpoints',
  description: 'Old API v1 endpoints still in use, need migration',
  category: 'architecture',
  severity: 'high',
  effort: 40,
  impact: 8,
  interest: 5
});

assessment.addDebtItem({
  id: 'debt-2',
  title: 'Missing unit tests',
  description: '30% of codebase lacks test coverage',
  category: 'test',
  severity: 'medium',
  effort: 80,
  impact: 7,
  interest: 3
});

console.log(assessment.generateReport());

2. Code Quality Scanner

import * as ts from 'typescript';
import * as fs from 'fs';

interface QualityIssue {
  file: string;
  line: number;
  issue: string;
  severity: 'info' | 'warning' | 'error';
  debtHours: number;
}

class CodeQualityScanner {
  private issues: QualityIssue[] = [];

  scanProject(directory: string): QualityIssue[] {
    this.issues = [];

    const files = this.getTypeScriptFiles(directory);

    for (const file of files) {
      this.scanFile(file);
    }

    return this.issues;
  }

  private scanFile(filePath: string): void {
    const sourceCode = fs.readFileSync(filePath, 'utf-8');
    const sourceFile = ts.createSourceFile(
      filePath,
      sourceCode,
      ts.ScriptTarget.Latest,
      true
    );

    // Check for anti-patterns
    this.checkForAnyTypes(sourceFile, filePath);
    this.checkForLongFunctions(sourceFile, filePath);
    this.checkForMagicNumbers(sourceFile, filePath);
    this.checkForConsoleStatements(sourceFile, filePath);
    this.checkForTodoComments(sourceFile, filePath);
  }

  private checkForAnyTypes(sourceFile: ts.SourceFile, filePath: string): void {
    const visit = (node: ts.Node) => {
      if (ts.isTypeReferenceNode(node) && node.typeName.getText() === 'any') {
        const { line } = ts.getLineAndCharacterOfPosition(
          sourceFile,
          node.getStart()
        );

        this.issues.push({
          file: filePath,
          line: line + 1,
          issue: 'Use of any type reduces type safety',
          severity: 'warning',
          debtHours: 0.5
        });
      }

      ts.forEachChild(node, visit);
    };

    visit(sourceFile);
  }

  private checkForLongFunctions(sourceFile: ts.SourceFile, filePath: string): void {
    const visit = (node: ts.Node) => {
      if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {
        if (node.body) {
          const lines = node.body.getFullText().split('\n').length;

          if (lines > 50) {
            const { line } = ts.getLineAndCharacterOfPosition(
              sourceFile,
              node.getStart()
            );

            this.issues.push({
              file: filePath,
              line: line + 1,
              issue: `Function has ${lines} lines, should be refactored`,
              severity: 'warning',
              debtHours: Math.ceil(lines / 10)
            });
          }
        }
      }

      ts.forEachChild(node, visit);
    };

    visit(sourceFile);
  }

  private checkForMagicNumbers(sourceFile: ts.SourceFile, filePath: string): void {
    const visit = (node: ts.Node) => {
      if (ts.isNumericLiteral(node)) {
        const value = parseFloat(node.text);

        // Ignore common constants
        if (![0, 1, -1, 2].includes(value)) {
          const { line } = ts.getLineAndCharacterOfPosition(
            sourceFile,
            node.getStart()
          );

          this.issues.push({
            file: filePath,
            line: line + 1,
            issue: `Magic number ${value} should be a named constant`,
            severity: 'info',
            debtHours: 0.1
          });
        }
      }

      ts.forEachChild(node, visit);
    };

    visit(sourceFile);
  }

  private checkForConsoleStatements(sourceFile: ts.SourceFile, filePath: string): void {
    const text = sourceFile.getFullText();
    const lines = text.split('\n');

    lines.forEach((line, index) => {
      if (line.includes('console.log') || line.includes('console.error')) {
        this.issues.push({
          file: filePath,
          line: index + 1,
          issue: 'Console statement should use proper logger',
          severity: 'info',
          debtHours: 0.1
        });
      }
    });
  }

  private checkForTodoComments(sourceFile: ts.SourceFile, filePath: string): void {
    const text = sourceFile.getFullText();
    const lines = text.split('\n');

    lines.forEach((line, index) => {
      if (/\/\/\s*TODO/.test(line)) {
        this.issues.push({
          file: filePath,
          line: index + 1,
          issue: 'TODO comment indicates incomplete work',
          severity: 'warning',
          debtHours: 2
        });
      }
    });
  }

  private getTypeScriptFiles(dir: string): string[] {
    // Implementation
    return [];
  }

  getTotalDebt(): number {
    return this.issues.reduce((sum, issue) => sum + issue.debtHours, 0);
  }

  generateReport(): string {
    let report = '# Code Quality Report\n\n';

    const bySeverity = this.issues.reduce((acc, issue) => {
      acc[issue.severity] = acc[issue.severity] || [];
      acc[issue.severity].push(issue);
      return acc;
    }, {} as Record<string, QualityIssue[]>);

    report += `## Summary\n\n`;
    report += `- Total Issues: ${this.issues.length}\n`;
    report += `- Estimated Debt: ${this.getTotalDebt()} hours\n\n`;

    for (const [severity, issues] of Object.entries(bySeverity)) {
      report += `### ${severity.toUpperCase()} (${issues.length})\n\n`;

      for (const issue of issues.slice(0, 10)) {
        report += `- ${issue.file}:${issue.line} - ${issue.issue}\n`;
      }

      report += '\n';
    }

    return report;
  }
}

Best Practices

✅ DO

  • Quantify debt impact
  • Prioritize by ROI
  • Track debt over time
  • Include debt in sprints
  • Document debt decisions
  • Set quality gates

❌ DON'T

  • Ignore technical debt
  • Fix everything at once
  • Skip impact analysis
  • Make emotional decisions

Debt Categories

  • Code Quality: Complex code, duplicates
  • Architecture: Poor design, tight coupling
  • Testing: Missing tests, flaky tests
  • Documentation: Missing docs, outdated
  • Security: Vulnerabilities, outdated deps
  • Performance: Inefficient code, N+1 queries

Resources

Quick Install

/plugin add https://github.com/aj-geddes/useful-ai-prompts/tree/main/technical-debt-assessment

Copy and paste this command in Claude Code to install this skill

GitHub 仓库

aj-geddes/useful-ai-prompts
Path: skills/technical-debt-assessment

Related Skills

subagent-driven-development

Development

This skill executes implementation plans by dispatching a fresh subagent for each independent task, with code review between tasks. It enables fast iteration while maintaining quality gates through this review process. Use it when working on mostly independent tasks within the same session to ensure continuous progress with built-in quality checks.

View skill

algorithmic-art

Meta

This Claude Skill creates original algorithmic art using p5.js with seeded randomness and interactive parameters. It generates .md files for algorithmic philosophies, plus .html and .js files for interactive generative art implementations. Use it when developers need to create flow fields, particle systems, or other computational art while avoiding copyright issues.

View skill

executing-plans

Design

Use the executing-plans skill when you have a complete implementation plan to execute in controlled batches with review checkpoints. It loads and critically reviews the plan, then executes tasks in small batches (default 3 tasks) while reporting progress between each batch for architect review. This ensures systematic implementation with built-in quality control checkpoints.

View skill

cost-optimization

Other

This Claude Skill helps developers optimize cloud costs through resource rightsizing, tagging strategies, and spending analysis. It provides a framework for reducing cloud expenses and implementing cost governance across AWS, Azure, and GCP. Use it when you need to analyze infrastructure costs, right-size resources, or meet budget constraints.

View skill