Tech Debt Analysis
Perform a structural analysis of the codebase to identify tech debt — areas where design quality has degraded and future development velocity is at risk.
Grounded in principles from:
- Martin Fowler's Refactoring — code smell detection (Long Method, Large Class, Duplicated Code, Long Parameter List, etc.)
- Design Stamina Hypothesis — good design pays off by keeping development speed high over time
- AWS Builders' Library — minimize blast radius through loose coupling and clear module boundaries
This is a read-only diagnostic skill — it does not modify code. Use its output to prioritize refactoring work via /plan-feature.
Arguments
$ARGUMENTS - Optional flags:
--analyzer <list>(comma-separated analyzers; default: all)--severity <level>(minimum severity: critical, high, medium, low, info; default: low)--project-dir <path>(directory to analyze; default: auto-detect)--out-dir <path>(default:docs/tech-debt)--format <md|json|both>(default: both)--no-parallel(run analyzers sequentially)
Valid analyzers: complexity, coupling, duplication, imports
Script Location
Scripts live in <agent-skills-dir>/tech-debt-analysis/scripts/. Each agent runtime substitutes <agent-skills-dir> with its config directory:
- Claude:
.claude/skills - Codex:
.codex/skills - Gemini:
.gemini/skills
Installed skill copies are expected to include these scripts. If they are missing, reinstall the skill from its canonical distribution rather than invoking a repo-local path.
Prerequisites
- Python 3.11+
- For the
couplinganalyzer: architecture artifacts must exist (docs/architecture-analysis/architecture.graph.json). Run/refresh-architecturefirst if missing. - No external dependencies — uses only Python stdlib (
ast,hashlib,json,pathlib)
Analyzers
1. Complexity Analyzer (complexity)
Uses Python's ast module to detect:
| Code Smell | Metric | Threshold | Critical | Reference |
|---|---|---|---|---|
| Long Method | Function line count | 50 | 100 | Fowler: Extract Method |
| Large File / God File | File line count | 500 | 1000 | Fowler: Extract Class |
| Complex Function | McCabe cyclomatic complexity | 10 | 20 | Fowler: Decompose Conditional |
| Deep Nesting | Control-flow nesting depth | 4 | 6 | Fowler: Guard Clauses |
| Long Parameter List | Parameter count (excl. self/cls) | 5 | 8 | Fowler: Introduce Parameter Object |
| Too Many Definitions | Top-level classes + functions | 20 | 40 | SRP: Single Responsibility Principle |
2. Coupling Analyzer (coupling)
Reads from existing architecture artifacts to detect:
| Code Smell | Metric | Threshold | Reference |
|---|---|---|---|
| High Fan-out | Outgoing dependencies | 10 | Shotgun Surgery / Feature Envy |
| High Fan-in | Incoming dependents | 10 | Change Amplifier |
| Hub Node | High fan-in AND fan-out | 8 each | God Object / Blob |
| High Impact | Transitive dependents | 15 | AWS: Blast Radius |
Requires: docs/architecture-analysis/architecture.graph.json (from /refresh-architecture)
3. Duplication Analyzer (duplication)
Uses structural fingerprinting to detect copy-pasted code:
- Normalizes source (strip comments, collapse whitespace, abstract literals)
- Extracts sliding windows of 6 consecutive normalized lines
- Groups by fingerprint hash to find exact structural duplicates
- Reports cross-file vs same-file duplication
4. Import Analyzer (imports)
Builds a module-level import graph to detect:
| Code Smell | Description | Reference |
|---|---|---|
| Circular Import | Cycles in the import graph | Fragile initialization order |
| Import Fan-out | Module importing 15+ other modules | Divergent Change |
| Star Import | from X import * |
Namespace Pollution |
Steps
0. Ensure Fresh Architecture Artifacts
The coupling analyzer reads architecture.graph.json rather than the source, so
its freshness is this skill's responsibility at the moment of reading — no gate or
sync point keeps it current on your behalf:
# Ensure architecture artifacts are current, immediately before the first read.
# `--ensure` is `--check` plus a staged refresh only when the check is not fresh,
# so on an already-fresh checkout it writes nothing. PYTHON must name the same
# interpreter this repository's architecture targets use: the check runs in-process
# and the pipeline runs in a subprocess, and if the two disagree about which
# optional grammars are importable they report permanent, unfixable drift.
ARCH_PY="${PYTHON:-python3}"
if "$ARCH_PY" "<agent-skills-dir>/refresh-architecture/scripts/run_architecture.py" --ensure --python "$ARCH_PY"; then
ARCH_FRESHNESS="ensured"
else
ARCH_FRESHNESS="DEGRADED"
echo "DEGRADED: architecture artifacts could not be made current; the last known-good analysis is left intact but unverified. Report every architecture-derived finding below as unverified rather than as current." >&2
fi
The coupling analyzer also detects stale artifacts (> 7 days old) and warns in its output, but that warning is a backstop: the ensure call above is what makes the fan-in/fan-out figures describe the tree you are analyzing.
1. Run Orchestrator
python3 <agent-skills-dir>/tech-debt-analysis/scripts/main.py \
--analyzer <analyzers-or-omit-for-all> \
--severity <level> \
--project-dir <path> \
--out-dir docs/tech-debt \
--format both
2. Review Report
The orchestrator produces:
docs/tech-debt/tech-debt-report.md— human-readable report with hotspots, severity breakdown, and refactoring recommendationsdocs/tech-debt/tech-debt-report.json— machine-readable for downstream tools
3. Interpret Results
Severity Levels (descending): critical > high > medium > low > info
Severity mapping:
- Metric ≥ 2× threshold → high (active pain point)
- Metric ≥ threshold → medium (accumulating debt)
- Below threshold → not reported (unless severity filter is
info)
Hotspot files: Files with the most findings across all analyzers. These are the best candidates for refactoring investment.
4. Next Steps
- Quick wins: Address high-severity Long Method and Complex Function findings — these directly impact bug rates
- Structural: Use hotspot files to plan Extract Class / Move Method refactorings
- Coupling: Hub nodes and high-impact nodes need stable interfaces before further feature work
- Plan refactoring: Create a
/plan-featureproposal for significant refactoring efforts - Track over time: Re-run periodically and compare JSON reports to measure design stamina
5. Remediation Routing
Do not send every finding to /plan-feature. Route by blast radius and skill contract:
| Finding class | Typical analyzers / smells | Next skill |
|---|---|---|
| Local clarity / complexity | Long Method, Deep Nesting, Complex Function (single file, small surface) | /simplify — behavior-preserving polish with coverage gate + dual-run |
| Local duplication | Same-file or few-file structural duplicates under Rule of 500 | /simplify (isomorphic extract) after characterization pins all sites |
| Structural redesign | Large Class / God File, multi-module Extract Class, high fan-out redesign | /plan-feature — needs proposal, design, review gates |
| Coupling hubs | High fan-in/out, hub nodes, high-impact transitive dependents | /plan-feature (stabilize interfaces first); optional /refresh-architecture before re-analysis |
| Dead / zombie public surfaces | Unused public API with external consumers, orphan systems | /deprecation-and-migration — Hyrum's Law + migration, not silent delete |
| Measured performance debt | Hot paths with budgets or known p95 pain | /performance-optimization — measure before rewrite |
Quick-win rule of thumb: if the fix fits Rule of 500 (≤500 lines, ≤5 files) and must not change behavior, prefer /simplify. If the change rewrites module boundaries or public contracts, plan it.
When citing a finding in a follow-up PR, include the finding ID from tech-debt-report.json so report → remediation stays traceable.
Integration with Bug Scrub
This skill complements /bug-scrub:
- Bug scrub collects runtime signals (test failures, lint errors, type errors)
- Tech debt analysis collects structural signals (complexity, coupling, duplication)
Together, they provide a complete picture of codebase health. Run both before major planning sessions.
Integration with Architecture Analysis
The coupling analyzer reads directly from /refresh-architecture artifacts. For best results:
- Run
/refresh-architectureto update the graph - Run
/tech-debt-analysisto analyze structural quality - Review both reports together for a complete architectural assessment
Quality Checks
python3 -m pytest <agent-skills-dir>/tech-debt-analysis/tests -q
Common Rationalizations
| Rationalization | Why it's wrong |
|---|---|
| "Our CI doesn't fail on complexity, so high cyclomatic complexity isn't a problem" | The Design Stamina Hypothesis: design quality affects velocity over time, not pass/fail today. CI cares about correctness now; tech-debt analysis catches the velocity decay CI cannot see. |
| "Duplication is fine — the duplicated code is short" | Even short duplicates compound: a 6-line idiom copied 12 times becomes 72 lines that must change together at the next requirement shift. Fowler's Rule of Three exists to prevent that compounding. |
| "We'll fix the hub node when we have time" | Hub nodes have the largest blast radius (AWS Builders' Library); they degrade every dependent feature simultaneously. Refactor them first, not last. |
| "I'll skip refreshing architecture artifacts — the graph is probably fine" | The coupling analyzer reads stale fan-in/fan-out from a stale graph. Stale graph → stale findings → wrong refactoring priorities. The skill explicitly warns when artifacts are >7 days old; honor the warning. |
| "Star imports are a stylistic preference" | Star imports break the import analyzer's ability to detect what depends on what; they also defeat IDE rename refactoring. They are a real design defect, not style. |
Red Flags
- A
tech-debt-report.jsonproduced withoutdocs/architecture-analysis/architecture.graph.jsonbeing recent — the coupling analyzer either silently skipped or read stale data. - The report shows zero findings from the
complexityanalyzer on a codebase >5k LOC — almost certainly the analyzer ran on the wrong--project-diror excluded all source files. - Hotspot files in the report are never cited in the next refactoring proposal — the analysis became theater.
- A refactoring PR that claims to address tech debt but does not reference a specific finding ID from the report — no traceability between report and remediation.
- Circular import findings are accepted with
# noqainstead of resolved — fragile initialization order will eventually break in a new test runner / Python version.
Verification
- Confirm
docs/architecture-analysis/architecture.graph.jsonexists and is <7 days old before running thecouplinganalyzer (the report's metadata includes a freshness flag — check it). - Confirm the report's
findingsarray is non-empty for a non-trivial codebase, AND that at least two analyzers contributed findings (single-analyzer reports usually mean the others crashed silently). - Confirm any refactoring proposal derived from the report cites specific finding IDs (e.g., "addresses tech-debt-finding-12, tech-debt-finding-17") — traceable from report to remediation.
- Confirm the hotspot-files list at the top of the report was used to scope the refactoring — picking a non-hotspot file usually means the analysis was ignored.
- Confirm the report was re-run after the refactoring lands and the addressed findings disappeared (or moved below threshold) — closes the loop on Design Stamina.