Project Info
Inspiration
Every engineer has inherited a codebase they didn't write. You open a file, it's 400 lines long, there are no tests, the last dependency update was two years ago, and there's a TODO from 2021 that says "fix this later." The challenge isn't finding problems. It's knowing which ones matter most, why they matter, and how to fix them. We built DebtLens to do all three.
What it does
The biggest lesson was the difference between decorating with math and building with math. We don't send raw files to Claude and ask "find debt." We compute real static signals first: severity = 0.35 * C + 0.30 * T + 0.20 * D + 0.15 * Doc The LLM reasons on top of those signals, not instead of them. Every finding is grounded in specific line numbers you can verify. We also learned that scope discipline is a feature — cutting from seven node types to four debt categories gave us the time to build auto-fix properly instead of shipping seven shallow things.
How we built it
Three layers, each independently verifiable: Static Analysis — the backend computes raw metrics per file (nesting depth, LOC, test file existence, dependency age) before touching the LLM. The LLM gets structured signals, not raw source. Schema-Constrained Extraction — Claude must cite specific line numbers to report a finding, or return nothing. Line references are validated server-side. No hallucinated findings reach the UI. Auto-Fix Pipeline — a second LLM call generates a complete file rewrite with a self-reported confidence score. Two gates run before the user sees anything: a confidence floor ( < 65\% ) drops the fix, and a syntax check (node --check, py_compile) deletes the branch on failure. On Apply, the branch pushes to GitHub and opens a pre-filled PR. On Discard, the temp directory is deleted entirely.
Challenges we ran into
JSON compliance. Claude occasionally responded to the auto-fix prompt with prose instead of JSON. Fix: strengthened system prompt plus a resilient parser that extracts the first & to last } as a fallback before throwing. Scratch repo isolation. DebtLens never has a full clone — only file content from the API. We build a scratch git repo containing just the file under fix, apply the rewrite, run the syntax gate, and push only that branch to GitHub on Apply. ESM syntax checking. node --check on ESM files in a bare scratch repo fails because Node defaults to CommonJS. Fix: inject a minimal {"type": "module" } package.json into the scratch repo before checking. Prompt specificity. Generic suggestions produce useless auto-fixes. We treated extraction quality as a hard gate - no auto-fix pipeline until every refactorSuggestion referenced exact line numbers and function names specific enough to act on without clarification.
Accomplishments we're proud of
The auto-fix pipeline working end to end on a real repo — branch isolation, confidence floor, syntax gate, and GitHub PR in one flow. That's not a demo feature, that's a production-grade safety architecture built in a hackathon timeline.Getting the LLM to reason on top of static signals rather than replacing them. Every finding cites real line numbers validated server-side. No hallucinated debt items reach the UI.Treating extraction quality as a hard gate before building auto-fix. We didn't start Step 9 until every suggestion was specific enough to act on without clarification. That discipline is why the auto-fix produces meaningful diffs instead of noise.
What we learned
Scope discipline is a feature. Cutting from seven node types to four debt categories gave us the time to build each one with real depth — and enough runway for auto-fix. The difference between decorating with math and building with math. A severity formula means nothing if the inputs aren't grounded in real computed metrics. We built the static analysis pass first precisely so the LLM had something concrete to reason about. Prompt compliance is an engineering problem, not a prompt problem. When Claude returned prose instead of JSON, the fix wasn't just a better prompt — it was a resilient parser that extracts { to } as a fallback. Robust systems don't rely on perfect model behavior.
What's next
Model flexibility. Engineers should be able to bring their own model — Claude, GPT-4o, Gemini, or a self-hosted Llama variant for teams with strict data privacy requirements. The LLM call is already isolated to a single module, so swapping the provider is an interface change, not an architectural one. A settings panel would let users paste in their own API key and select their preferred model without touching the codebase. Private repo support via GitHub OAuth, so teams can scan their actual production codebases rather than only public repos. IDE integration — a VS Code extension that surfaces debt items inline as you edit, rather than requiring a separate analysis run. Trend tracking — re-scan a repo over time and show whether the health score is improving or degrading sprint over sprint. Debt that's growing is more urgent than debt that's stable. Team dashboards — aggregate scores across multiple repos so engineering leads can see which codebases need the most attention at a glance.
DebtLens
A technical-debt scanner for GitHub repositories. DebtLens traverses a repo's source, runs a deterministic static-analysis pass to compute real metrics, then uses the Anthropic API to reason on top of those signals — not raw files — and produce a structured, line-grounded debt report.
The architectural claim: every finding is grounded in specific line numbers. The LLM never invents debt it can't cite to a metric or a line. Out-of-bounds line refs are dropped before they ever reach the UI.
What it does
- Repo health dashboard — an overall 0–100 score plus four category scores (Complexity, Test Coverage, Dependencies, Documentation) with color bands.
- Prioritized debt list — every finding with a severity badge, category tag, file path, and one-line summary; sortable and filterable by category.
- File drilldown — full source with the referenced lines highlighted, next to the debt item's reasoning and a specific, actionable refactor suggestion.
- Auto-fix (Step 9) — for a given debt item, generate a complete rewritten file, apply it to an isolated throwaway branch, run a syntax gate, and show a unified diff with a confidence score. It never touches your repo. You can then push the fix to a branch on GitHub (compare URL returned) or download the fixed file.
How it works
GitHub URL
│
▼
github.js fetch the N most-recently-modified source files
│ (N is user-chosen, default 30, capped at 50; skips
│ node_modules, lockfiles, config, binaries)
▼
staticAnalysis.js per-file metrics — loc, functionCount, maxNestingDepth,
│ todoCount, hasTestFile, docstringRatio, dependencyAge
▼
index.js per-category file selection (each criterion is a metric)
│
▼
llmExtractor.js Anthropic call per file → debt items, line-ref validated
│
▼
scorer.js severity formula + repo health + category scores
│
▼
server.js streams progress + final report to the React frontend (SSE)
Severity formula (scorer.js, weights are tunable):
severity = complexity·0.35 + testDebt·0.30 + depDebt·0.20 + docDebt·0.15
where complexity = norm(loc × maxNestingDepth), testDebt = 0|100 by whether
a test file exists, depDebt = norm(maxDependencyAge), and
docDebt = (1 − docstringRatio)·100. Overall repo health = 100 − mean(fileScores).
Project structure
DebtLens/
├── backend/
│ ├── server.js # Express server — /analyze (SSE) + auto-fix routes
│ ├── index.js # Pipeline orchestration + CLI driver
│ ├── github.js # GitHub API traversal (default 30 files, cap 50, in-memory cache)
│ ├── staticAnalysis.js # Deterministic per-file metrics (no LLM)
│ ├── llmExtractor.js # Anthropic calls + line-ref validation (4 categories)
│ ├── scorer.js # Severity formula + repo/category scoring
│ ├── env.js # Zero-dependency .env loader
│ └── autoFix/
│ ├── fixGenerator.js # LLM call → full rewritten file (structured output)
│ ├── fixApplier.js # Scratch git repo + isolated branch + syntax gate
│ ├── diffBuilder.js # Unified diff via the `diff` package
│ └── githubPush.js # Push fix to a GitHub branch (or download fallback)
└── frontend/
└── src/
├── App.jsx
└── components/
├── RepoInput.jsx # URL + file-count input (demo pre-loaded)
├── HealthDashboard.jsx # Overall + 4 category score cards
├── DebtList.jsx # Sortable / filterable debt list
├── FileDrilldown.jsx # Source viewer with line highlighting
└── AutoFixPanel.jsx # Diff viewer + apply/discard controls
Getting started
Prerequisites
- Node.js ≥ 20.6 (the backend uses the built-in
process.loadEnvFile) - An Anthropic API key (uses
claude-sonnet-4-6) - (Optional) a GitHub token — raises the API rate limit from 60 to 5000 req/hour, and is required to push auto-fixes to a branch
1. Backend
cd backend
npm install
cp .env.example .env # then fill in ANTHROPIC_API_KEY (and optionally a token)
npm run server # starts the API on http://localhost:3001
.env keys:
| Variable | Purpose |
|---|---|
ANTHROPIC_API_KEY | Required — LLM extraction and fix generation |
DEBTLENS_GITHUB_TOKEN | Optional — preferred GitHub token (read for analysis, write for Apply) |
GITHUB_TOKEN | Optional — fallback if DEBTLENS_GITHUB_TOKEN is unset |
Apply-to-GitHub needs write access (
repo/public_reposcope) and only works on repos your token can push to (yours or a fork). For repos you don't own, Apply falls back to downloading the fixed file.
2. Frontend
cd frontend
npm install
npm run dev # Vite dev server; proxies /analyze and /api to :3001
Open the printed URL, paste a public GitHub repo (pre-loaded with
expressjs/express), pick a file count (1–50, defaults to 30), and hit
Analyze.
CLI (no frontend)
The backend doubles as a CLI that prints the full report to the console:
cd backend
node index.js https://github.com/expressjs/express
# or the shortcuts:
npm run demo # sindresorhus/slugify
npm run demo:express # expressjs/express
API
| Method | Route | Description |
|---|---|---|
POST | /analyze | { repoUrl, fileCount } → SSE stream of progress events then a done event with the full report |
POST | /api/autofix | { file, debtItem } → generate + apply (isolated branch) + syntax-check + diff |
POST | /api/autofix/push | { repoPath, branch, owner, repo, fix } → push to a GitHub branch (or download fallback) |
DELETE | /api/autofix/discard | { repoPath, branch } → tear down the temp branch + scratch repo |
Auto-fix safety invariants
Auto-fix is built so a bad suggestion can never corrupt your code:
- The original repo is never cloned or written to — DebtLens only ever holds
file contents. Each fix goes into a throwaway scratch git repo containing
just the one file, on a
debtlens/autofix-{timestamp}branch. - No diff is shown unless the syntax check passes (
node --check,python -m py_compile,go vet; unchecked languages cap confidence at 70). - Fixes with
confidence < 65, a null rewrite, or no overlap with the original line refs are dropped server-side, with the reason shown to the user. - Confidence is always visible. The temp branch is always deleted on discard.
Key constraints
- File cap of 50, with a default of 30 — adjustable in the UI, always surfaced and enforced server-side ("most recently modified files" — a defensible recency heuristic).
- Line-ref validation: any finding referencing an out-of-bounds line is dropped and logged — never displayed.
- Rate limits: GitHub is 60 req/hour unauthenticated; file contents are cached in memory for the session, and a token raises the limit to 5000/hour.
Tech stack
- Backend: Node.js + Express,
@anthropic-ai/sdk,simple-git,diff - Frontend: React 18 + Vite
- APIs: GitHub REST API, Anthropic API (
claude-sonnet-4-6)
Analysis
View
Metric
- 21
- 17
Figures cover GitHub contributors during the hackathon window. A co-authored commit counts in full for each author, so per-member totals add up to more than the whole-team figures.
Technology
- AnthropicIn code
- CSSIn code
- ExpressIn code
- HTMLIn code
- JavaScriptIn code
- ReactIn code
- Node.jsClaimed
6 of 7 appear in the indexed code. 1 claimed on Devpost could not be matched to code, which may simply mean the tool leaves no trace in the repository.
AI coding agents
- Claude CodeConfig · Commits
Detected from committed agent config files and commit authorship. Absence of a signal is not proof an agent was unused.
Codebase size
Source size
185 KB
Source files
27
Counts recognized source files only; vendored directories, binaries and lockfiles are excluded, so this is smaller than the repository on disk.
Repository
backSpaceTwice/DebtLens
33 files · 279 KB · @ 3a9ee91
Structure
Interface
8 files · 24%Screens, components and styles rendered to the user.
Application logic
15 files · 45%Domain rules, services and shared utilities.
Supporting
Layers are inferred from where files sit in the tree, not from reading the code. A project that names its directories unconventionally will read oddly here — open the file browser to check anything the diagram implies.
Languages
- JavaScript67%
- CSS17%
- Markdown15%
- HTML0%
Share of indexed source by file size. Binary and vendored files are excluded.
Dependencies
backend/package.json
npm · 5- @anthropic-ai/sdk
- cors
- diff
- express
- simple-git
frontend/package.json
npm · 4- react
- react-dom
- +2 more
Declared in the repository’s manifests at the indexed commit. A declared package is not proof it is used, and runtime dependencies are listed first.
This project’s features have not been analysed yet.
Export this project's context (description, README, evidence, key source files) to chat with an AI agent elsewhere.