tech-debt-visualizer 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -79,6 +79,9 @@ Requires **Node 18+**.
79
79
  | `-f, --format` | `cli` (default), `html`, `json`, or `markdown` |
80
80
  | `-o, --output` | Output path (e.g. `report.html` for HTML) |
81
81
  | `--no-llm` | Skip all LLM calls (no API key needed) |
82
+ | `--llm` | Enable LLM (default). Use with `--llm-key` and/or `--llm-model` |
83
+ | `--llm-key <key>` | API key (overrides env / `.env`) |
84
+ | `--llm-model <model>` | Model name (e.g. `gemini-1.5-flash`, `gpt-4o-mini`) |
82
85
  | `--ci` | Terse output; exit code 1 if debt score &gt; 60 |
83
86
 
84
87
  Examples:
@@ -87,6 +90,10 @@ Examples:
87
90
  node dist/cli.js analyze . -f html -o report.html
88
91
  node dist/cli.js analyze ./src -f json -o debt.json
89
92
  node dist/cli.js analyze . --ci
93
+ # With LLM key and model on the command line:
94
+ node dist/cli.js analyze . --llm-key YOUR_GEMINI_KEY --llm-model gemini-1.5-flash
95
+ # Or use a .env file in the current directory (GEMINI_API_KEY=...)
96
+ node dist/cli.js analyze .
90
97
  ```
91
98
 
92
99
  ---
@@ -95,6 +102,8 @@ node dist/cli.js analyze . --ci
95
102
 
96
103
  The tool can call an LLM to get **explanations** and **concrete code refactor suggestions**. You only need one provider; the first one with a key wins.
97
104
 
105
+ **Ways to pass the API key:** (1) Put `GEMINI_API_KEY=your_key` (or `OPENAI_API_KEY`, etc.) in a **`.env` file** in the directory you run the command from (current working directory). (2) **Export** in the same shell: `export GEMINI_API_KEY=your_key`. (3) **CLI flag**: `--llm-key your_key`. You can also set **model** via `--llm-model gemini-1.5-flash` (or another model name).
106
+
98
107
  | Provider | Env var(s) | Optional env |
99
108
  |----------|------------|---------------|
100
109
  | **OpenRouter** | `OPENROUTER_API_KEY` | `OPENROUTER_MODEL` (default: `google/gemini-2.0-flash-001`) |
package/dist/cli.d.ts CHANGED
@@ -1,5 +1,2 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * CLI entry: colorful terminal output, progress bars, actionable insights.
4
- */
5
- import "dotenv/config";
2
+ export {};
package/dist/cli.js CHANGED
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * CLI entry: colorful terminal output, progress bars, actionable insights.
4
+ * Loads .env from cwd first; supports --llm-key and --llm-model.
4
5
  */
5
- import "dotenv/config";
6
+ import { readFile } from "node:fs/promises";
7
+ import { join } from "node:path";
8
+ import { loadEnv } from "./load-env.js";
6
9
  import { Command } from "commander";
7
10
  import chalk from "chalk";
8
11
  import cliProgress from "cli-progress";
9
- import { readFile } from "node:fs/promises";
10
- import { join } from "node:path";
11
12
  import { getCleanlinessTier } from "./cleanliness-score.js";
12
13
  import { runAnalysis } from "./engine.js";
13
14
  import { assessFileCleanliness, assessOverallCleanliness, enrichDebtWithInsights, resolveLLMConfig, suggestNextSteps, } from "./llm.js";
@@ -18,7 +19,7 @@ const program = new Command();
18
19
  program
19
20
  .name("tech-debt")
20
21
  .description("Analyze repositories and visualize technical debt with AI-powered insights")
21
- .version("0.1.0");
22
+ .version("0.1.2");
22
23
  program
23
24
  .command("analyze")
24
25
  .description("Analyze a repository and output report")
@@ -26,6 +27,9 @@ program
26
27
  .option("-o, --output <path>", "Output file path (default: report.html or stdout for CLI)")
27
28
  .option("-f, --format <type>", "Output format: cli | html | json | markdown", "cli")
28
29
  .option("--no-llm", "Skip LLM-powered insights")
30
+ .option("--llm", "Enable LLM (default). Use with --llm-key and/or --llm-model")
31
+ .option("--llm-key <key>", "API key for LLM (overrides GEMINI_API_KEY / OPENAI_API_KEY / OPENROUTER_API_KEY)")
32
+ .option("--llm-model <model>", "Model name (e.g. gemini-1.5-flash, gpt-4o-mini)")
29
33
  .option("--ci", "CI mode: minimal output, exit with non-zero if debt score is high")
30
34
  .action(async (path, opts) => {
31
35
  const repoPath = join(process.cwd(), path);
@@ -57,11 +61,13 @@ program
57
61
  // ignore
58
62
  }
59
63
  }
64
+ const llmConfigOverrides = { apiKey: opts.llmKey, model: opts.llmModel };
60
65
  if (useLlm) {
61
- const llmConfig = resolveLLMConfig();
66
+ const llmConfig = resolveLLMConfig(llmConfigOverrides);
62
67
  if (!llmConfig) {
63
- process.stderr.write(chalk.yellow(" No LLM API key found. Set one of: GEMINI_API_KEY, OPENAI_API_KEY, or OPENROUTER_API_KEY.\n" +
64
- " Example: export GEMINI_API_KEY=your_key_here\n" +
68
+ process.stderr.write(chalk.yellow(" No LLM API key found. Set GEMINI_API_KEY or OPENAI_API_KEY (or use --llm-key <key>).\n" +
69
+ " Example: export GEMINI_API_KEY=your_key or --llm-key your_key\n" +
70
+ " Or add GEMINI_API_KEY=your_key to a .env file in the current directory.\n" +
65
71
  " Skipping AI insights for this run.\n\n"));
66
72
  }
67
73
  else {
@@ -69,11 +75,12 @@ program
69
75
  const allFilePaths = run.fileMetrics.map((m) => m.file);
70
76
  const maxFiles = 80;
71
77
  const filesToAssess = run.fileMetrics.slice(0, maxFiles);
78
+ const config = { ...llmConfigOverrides };
72
79
  for (const m of filesToAssess) {
73
80
  const content = fileContents.get(m.file);
74
81
  if (!content)
75
82
  continue;
76
- const result = await assessFileCleanliness(m.file, content, m, {}, { filePaths: allFilePaths });
83
+ const result = await assessFileCleanliness(m.file, content, m, config, { filePaths: allFilePaths });
77
84
  if (result) {
78
85
  const idx = run.fileMetrics.findIndex((x) => x.file === m.file);
79
86
  if (idx >= 0)
@@ -87,15 +94,15 @@ program
87
94
  progress.update(4, { task: "LLM: debt item insights..." });
88
95
  let debtItems = run.debtItems;
89
96
  if (debtItems.length > 0) {
90
- debtItems = await enrichDebtWithInsights(debtItems.slice(0, 25), fileContents);
97
+ debtItems = await enrichDebtWithInsights(debtItems.slice(0, 25), fileContents, config);
91
98
  const byId = new Map(debtItems.map((d) => [d.id, d]));
92
99
  run.debtItems = run.debtItems.map((d) => byId.get(d.id) ?? d);
93
100
  }
94
101
  progress.update(5, { task: "LLM: overall assessment..." });
95
- const overall = await assessOverallCleanliness(run);
102
+ const overall = await assessOverallCleanliness(run, config);
96
103
  if (overall)
97
104
  run.llmOverallAssessment = overall;
98
- const nextSteps = await suggestNextSteps(run);
105
+ const nextSteps = await suggestNextSteps(run, config);
99
106
  if (nextSteps?.length)
100
107
  run.llmNextSteps = nextSteps;
101
108
  }
@@ -133,8 +140,7 @@ program
133
140
  else {
134
141
  printCliReport(run, opts.ci ?? false);
135
142
  if (!run.llmOverallAssessment) {
136
- process.stdout.write(chalk.dim(" To get AI insights, per-file optimization suggestions, and refactor recommendations:\n" +
137
- " set GEMINI_API_KEY or OPENAI_API_KEY and run without --no-llm.\n\n"));
143
+ process.stdout.write(chalk.dim(" To get AI insights: set GEMINI_API_KEY (or OPENAI_API_KEY) or use --llm-key <key>. Run without --no-llm.\n\n"));
138
144
  }
139
145
  if (opts.ci && getDebtScore(run) > 60)
140
146
  process.exit(1);
@@ -270,12 +276,19 @@ function severityColor(score) {
270
276
  }
271
277
  function cleanlinessTierColor(tier) {
272
278
  switch (tier) {
273
- case 5: return chalk.green.bold;
274
- case 4: return chalk.cyan.bold;
275
- case 3: return chalk.yellow.bold;
276
- case 2: return chalk.hex("#f97316").bold;
277
- case 1: return chalk.red.bold;
278
- default: return chalk.white.bold;
279
+ case 5:
280
+ return chalk.green.bold;
281
+ case 4:
282
+ return chalk.cyan.bold;
283
+ case 3:
284
+ return chalk.yellow.bold;
285
+ case 2:
286
+ return chalk.hex("#f97316").bold;
287
+ case 1:
288
+ return chalk.red.bold;
289
+ default:
290
+ return chalk.white.bold;
279
291
  }
280
292
  }
281
- program.parse();
293
+ // Load .env from cwd first, then run the CLI
294
+ loadEnv().then(() => program.parse());
@@ -0,0 +1 @@
1
+ export declare function loadEnv(): Promise<void>;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Load .env from current working directory. Uses dotenv if installed, else parses manually.
3
+ */
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ export async function loadEnv() {
7
+ const envPath = join(process.cwd(), ".env");
8
+ try {
9
+ const dotenv = await import("dotenv");
10
+ dotenv.config({ path: envPath });
11
+ return;
12
+ }
13
+ catch {
14
+ // dotenv not installed
15
+ }
16
+ if (!existsSync(envPath))
17
+ return;
18
+ const content = readFileSync(envPath, "utf-8");
19
+ for (const line of content.split("\n")) {
20
+ const trimmed = line.trim();
21
+ if (!trimmed || trimmed.startsWith("#"))
22
+ continue;
23
+ const eq = trimmed.indexOf("=");
24
+ if (eq <= 0)
25
+ continue;
26
+ const key = trimmed.slice(0, eq).trim();
27
+ let value = trimmed.slice(eq + 1).trim();
28
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")))
29
+ value = value.slice(1, -1);
30
+ process.env[key] = value;
31
+ }
32
+ }
@@ -17,10 +17,11 @@ function buildHtml(run, title, darkMode) {
17
17
  const theme = darkMode ? "dark" : "light";
18
18
  const debtScore = getDebtScore(run);
19
19
  const cleanliness = getCleanlinessTier(debtScore);
20
+ const hasLlm = !!(run.llmOverallAssessment || (run.llmNextSteps && run.llmNextSteps.length));
20
21
  const dataJson = JSON.stringify({
21
22
  fileMetrics: run.fileMetrics,
22
23
  debtItems: run.debtItems,
23
- debtTrend: run.debtTrend ?? [],
24
+ hasLlm,
24
25
  llmOverallAssessment: run.llmOverallAssessment ?? null,
25
26
  llmNextSteps: run.llmNextSteps ?? null,
26
27
  summary: {
@@ -40,7 +41,6 @@ function buildHtml(run, title, darkMode) {
40
41
  <meta charset="UTF-8" />
41
42
  <meta name="viewport" content="width=device-width, initial-scale=1" />
42
43
  <title>${escapeHtml(title)}</title>
43
- <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
44
44
  <style>
45
45
  :root {
46
46
  --bg: #0f0f12;
@@ -72,34 +72,34 @@ function buildHtml(run, title, darkMode) {
72
72
  letter-spacing: -0.01em;
73
73
  }
74
74
  .container { max-width: 1100px; margin: 0 auto; padding: 2.5rem 2rem; }
75
- .hero-caption { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.14em; color: var(--text-muted); margin: 0 0 0.75rem; font-weight: 600; }
76
- .hero {
77
- display: flex;
78
- align-items: stretch;
79
- gap: 0;
80
- margin-bottom: 2.5rem;
81
- border-radius: 16px;
82
- overflow: hidden;
75
+ .no-llm-banner {
76
+ width: 100%;
83
77
  background: var(--surface);
84
- border: 1px solid var(--border);
78
+ border-bottom: 2px solid var(--border);
79
+ padding: 0.75rem 1.5rem;
80
+ text-align: center;
85
81
  }
86
- .hero-grade {
87
- width: 140px;
88
- flex-shrink: 0;
89
- display: flex;
90
- flex-direction: column;
91
- align-items: center;
92
- justify-content: center;
93
- padding: 2rem 1.5rem;
82
+ .no-llm-banner .no-llm-cta { font-size: 1rem; font-weight: 600; color: var(--text); margin: 0; }
83
+ .hero-caption { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.1em; color: var(--text-muted); margin: 0 0 1rem; font-weight: 600; text-align: center; }
84
+ .hero {
85
+ text-align: center;
86
+ margin-bottom: 2rem;
87
+ }
88
+ .score-badge {
89
+ display: inline-block;
90
+ width: 120px;
91
+ padding: 1rem 0.5rem 0.85rem;
94
92
  background: var(--tier-bg);
95
- border-right: 1px solid var(--border);
93
+ color: var(--tier-fg);
94
+ border: 2px solid var(--tier-fg);
95
+ border-radius: 8px;
96
+ margin-bottom: 0.75rem;
96
97
  }
97
- .hero-grade .num { font-size: 3rem; font-weight: 800; line-height: 1; color: var(--tier-fg); letter-spacing: -0.03em; }
98
- .hero-grade .of { font-size: 0.75rem; color: var(--tier-fg); opacity: 0.7; margin-top: 0.25rem; text-transform: uppercase; letter-spacing: 0.1em; }
99
- .hero-body { flex: 1; padding: 2rem 2.25rem; min-width: 0; }
100
- .hero-body .score-label { font-size: 1.35rem; font-weight: 700; color: var(--text); margin: 0 0 0.35rem; letter-spacing: -0.02em; }
101
- .hero-body .score-desc { font-size: 0.9rem; color: var(--text-muted); line-height: 1.45; margin: 0; }
102
- .hero-body .report-meta { font-size: 0.8rem; color: var(--text-muted); margin-top: 1.25rem; }
98
+ .score-badge .num { display: block; font-size: 3rem; font-weight: 700; line-height: 1; letter-spacing: -0.03em; text-align: center; }
99
+ .score-badge .of { display: block; font-size: 0.75rem; font-weight: 600; text-align: center; text-transform: uppercase; letter-spacing: 0.08em; margin-top: 0.25rem; opacity: 0.9; }
100
+ .hero .score-label { font-size: 1.25rem; font-weight: 700; color: var(--text); margin: 0 0 0.25rem; letter-spacing: -0.01em; }
101
+ .hero .score-desc { font-size: 0.9rem; color: var(--text-muted); line-height: 1.5; margin: 0 0 0.5rem; max-width: 380px; margin-left: auto; margin-right: auto; }
102
+ .hero .report-meta { font-size: 0.8rem; color: var(--text-muted); }
103
103
  .hero.tier-1 { --tier-bg: #1c1917; --tier-fg: #f87171; }
104
104
  .hero.tier-2 { --tier-bg: #1c1917; --tier-fg: #fb923c; }
105
105
  .hero.tier-3 { --tier-bg: #1c1917; --tier-fg: #facc15; }
@@ -138,28 +138,29 @@ function buildHtml(run, title, darkMode) {
138
138
  #treemap {
139
139
  display: flex;
140
140
  flex-wrap: wrap;
141
- gap: 4px;
142
- min-height: 280px;
141
+ gap: 8px;
142
+ min-height: 200px;
143
+ padding: 0.5rem 0;
143
144
  }
144
145
  .treemap-cell {
145
- border-radius: 6px;
146
+ border-radius: 8px;
146
147
  display: flex;
147
148
  align-items: flex-end;
148
- padding: 6px 8px;
149
- font-size: 0.7rem;
149
+ min-width: 72px;
150
+ padding: 8px 10px;
151
+ font-size: 0.72rem;
150
152
  cursor: pointer;
151
- transition: transform 0.15s, filter 0.15s;
153
+ transition: transform 0.15s, filter 0.15s, box-shadow 0.15s;
152
154
  overflow: hidden;
153
155
  text-overflow: ellipsis;
154
156
  white-space: nowrap;
155
157
  }
156
- .treemap-cell:hover { transform: scale(1.03); filter: brightness(1.1); }
158
+ .treemap-cell:hover { transform: scale(1.04); filter: brightness(1.1); box-shadow: 0 4px 12px rgba(0,0,0,0.2); }
157
159
  .treemap-cell[data-severity="critical"] { background: linear-gradient(180deg, #ef4444 0%, #b91c1c 100%); color: #fff; }
158
160
  .treemap-cell[data-severity="high"] { background: linear-gradient(180deg, #f59e0b 0%, #d97706 100%); color: #fff; }
159
161
  .treemap-cell[data-severity="medium"] { background: linear-gradient(180deg, #eab308 0%, #ca8a04 100%); color: #1a1a1f; }
160
162
  .treemap-cell[data-severity="low"] { background: linear-gradient(180deg, #22c55e 0%, #16a34a 100%); color: #fff; }
161
163
  .treemap-cell[data-severity="none"] { background: var(--border); color: var(--text-muted); }
162
- #trendChart { max-height: 220px; }
163
164
  .debt-list { list-style: none; padding: 0; margin: 0; }
164
165
  .debt-list li {
165
166
  border-bottom: 1px solid var(--border);
@@ -206,23 +207,15 @@ function buildHtml(run, title, darkMode) {
206
207
  #detail .panel .file-assessment strong { color: var(--text); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em; }
207
208
  #detail .panel .suggested-code { margin-top: 1rem; padding: 0.75rem; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; font-size: 0.8rem; overflow-x: auto; }
208
209
  #detail .panel .suggested-code pre { margin: 0; white-space: pre-wrap; }
209
- .llm-overall .llm-overall-text { margin: 0; color: var(--text); line-height: 1.6; }
210
+ .llm-overall { border-left: 4px solid var(--accent); }
211
+ .llm-overall h2 { color: var(--accent); }
212
+ .llm-overall .llm-overall-text { margin: 0; color: var(--text); line-height: 1.6; font-size: 1rem; }
213
+ .llm-next-steps h2 { color: var(--accent); }
214
+ .llm-next-steps ul { margin: 0; padding-left: 1.25rem; color: var(--text); line-height: 1.6; }
210
215
  .priority-matrix { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-top: 1rem; }
211
216
  .priority-matrix .quadrant { padding: 1rem; border-radius: 8px; border: 1px solid var(--border); }
212
217
  .priority-matrix .quadrant h4 { margin: 0 0 0.5rem; font-size: 0.9rem; }
213
218
  .priority-matrix .quadrant p { margin: 0; font-size: 0.8rem; color: var(--text-muted); }
214
- .glossary {
215
- background: var(--surface);
216
- border: 1px solid var(--border);
217
- border-radius: 12px;
218
- padding: 1.25rem 1.5rem;
219
- margin-bottom: 1.5rem;
220
- }
221
- .glossary h2 { font-size: 0.95rem; margin: 0 0 0.75rem; color: var(--text-muted); font-weight: 600; }
222
- .glossary dl { margin: 0; font-size: 0.875rem; line-height: 1.6; }
223
- .glossary dt { font-weight: 600; color: var(--text); margin-top: 0.5rem; }
224
- .glossary dt:first-child { margin-top: 0; }
225
- .glossary dd { margin: 0.2rem 0 0 1rem; color: var(--text-muted); }
226
219
  .section-desc { font-size: 0.875rem; color: var(--text-muted); margin: -0.5rem 0 1rem; line-height: 1.45; }
227
220
  .legend { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; margin-bottom: 1rem; font-size: 0.8rem; color: var(--text-muted); }
228
221
  .legend span { display: inline-flex; align-items: center; gap: 0.35rem; }
@@ -235,18 +228,21 @@ function buildHtml(run, title, darkMode) {
235
228
  </style>
236
229
  </head>
237
230
  <body>
231
+ ${!hasLlm ? `
232
+ <div class="no-llm-banner">
233
+ <p class="no-llm-cta">Analysis run without LLM — for better results, run with LLM</p>
234
+ </div>
235
+ ` : ""}
238
236
  <div class="container">
239
237
  <p class="hero-caption">Technical Debt Cleanliness Score</p>
240
238
  <div class="hero tier-${cleanliness.tier}">
241
- <div class="hero-grade">
239
+ <div class="score-badge">
242
240
  <span class="num">${cleanliness.tier}</span>
243
241
  <span class="of">of 5</span>
244
242
  </div>
245
- <div class="hero-body">
246
- <div class="score-label">${escapeHtml(cleanliness.label)}</div>
247
- <p class="score-desc">${escapeHtml(cleanliness.description)}</p>
248
- <p class="report-meta">${escapeHtml(run.repoPath)} · ${run.completedAt ?? run.startedAt}</p>
249
- </div>
243
+ <div class="score-label">${escapeHtml(cleanliness.label)}</div>
244
+ <p class="score-desc">${escapeHtml(cleanliness.description)}</p>
245
+ <p class="report-meta">${escapeHtml(run.repoPath)} · ${run.completedAt ?? run.startedAt}</p>
250
246
  </div>
251
247
 
252
248
  <div class="summary-cards">
@@ -271,25 +267,9 @@ function buildHtml(run, title, darkMode) {
271
267
  </div>
272
268
  ` : ""}
273
269
 
274
- <div class="glossary">
275
- <h2>Understanding this report</h2>
276
- <dl>
277
- <dt>Debt score (0–100)</dt>
278
- <dd>Combined severity and confidence of all issues. Lower is better. &lt;40 = healthy, 40–70 = address soon, 70+ = high priority.</dd>
279
- <dt>Severity</dt>
280
- <dd><strong>Critical</strong> = fix first. <strong>High</strong> = plan soon. <strong>Medium/Low</strong> = backlog.</dd>
281
- <dt>Cyclomatic complexity</dt>
282
- <dd>Decision paths in code (if/else, loops). &gt;10 high, &gt;20 critical.</dd>
283
- <dt>Hotspot</dt>
284
- <dd>Files that change often and have high complexity—highest refactor risk.</dd>
285
- <dt>Trend chart</dt>
286
- <dd>Heuristic from recent commits; shows churn pattern, not full history.</dd>
287
- </dl>
288
- </div>
289
-
290
270
  <div class="section">
291
- <h2>Debt distribution by file</h2>
292
- <p class="section-desc">Each block is a file. <strong>Size</strong> = complexity + churn (larger = heavier). <strong>Color</strong> = worst severity in that file. Click a block to see details.</p>
271
+ <h2>Files by debt</h2>
272
+ <p class="section-desc">Size = complexity + churn. Color = worst severity. Click for details.</p>
293
273
  <div class="legend">
294
274
  <span><span class="swatch swatch-crit"></span> Critical</span>
295
275
  <span><span class="swatch swatch-high"></span> High</span>
@@ -300,12 +280,6 @@ function buildHtml(run, title, darkMode) {
300
280
  <div id="treemap"></div>
301
281
  </div>
302
282
 
303
- <div class="section">
304
- <h2>Debt trend (recent commits)</h2>
305
- <p class="section-desc">Estimated activity per commit (files changed). Rising pattern may indicate growing churn; not a full historical debt metric.</p>
306
- <canvas id="trendChart"></canvas>
307
- </div>
308
-
309
283
  <div class="section">
310
284
  <h2>Prioritized recommendations</h2>
311
285
  <p class="section-desc">Focus on high-impact items first. Easy wins = high severity but smaller files; harder = critical or hotspot files that need planning.</p>
@@ -355,38 +329,13 @@ function buildHtml(run, title, darkMode) {
355
329
  cell.className = 'treemap-cell';
356
330
  cell.dataset.severity = severity;
357
331
  cell.style.flex = String(score / maxScore * 100) + ' 1 80px';
358
- cell.style.minWidth = '80px';
332
+ cell.style.minWidth = '72px';
359
333
  cell.title = file;
360
334
  cell.textContent = file.split('/').pop() || file;
361
335
  cell.addEventListener('click', () => showDetail(file, items));
362
336
  treemap.appendChild(cell);
363
337
  });
364
338
 
365
- // Trend chart
366
- const ctx = document.getElementById('trendChart').getContext('2d');
367
- new Chart(ctx, {
368
- type: 'line',
369
- data: {
370
- labels: (DATA.debtTrend || []).map(t => t.commit),
371
- datasets: [{
372
- label: 'Debt score',
373
- data: (DATA.debtTrend || []).map(t => t.score),
374
- borderColor: '#6366f1',
375
- backgroundColor: 'rgba(99, 102, 241, 0.1)',
376
- fill: true,
377
- tension: 0.3,
378
- }]
379
- },
380
- options: {
381
- responsive: true,
382
- plugins: { legend: { display: false } },
383
- scales: {
384
- y: { beginAtZero: true, grid: { color: 'var(--border)' }, ticks: { color: 'var(--text-muted)' } },
385
- x: { grid: { color: 'var(--border)' }, ticks: { color: 'var(--text-muted)' } }
386
- }
387
- }
388
- });
389
-
390
339
  // Priority quadrants
391
340
  const highImpact = DATA.debtItems.filter(d => d.severity === 'high' || d.severity === 'critical').slice(0, 5);
392
341
  document.getElementById('q1').innerHTML = highImpact.slice(0, 3).map(d => '<li style="font-size:0.8rem">' + escapeHtml(d.file) + '</li>').join('');
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "tech-debt-visualizer",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Language-agnostic CLI that analyzes repos and generates interactive technical debt visualizations with AI-powered insights",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
- "tech-debt": "dist/cli.js"
8
+ "tech-debt": "dist/cli.js",
9
+ "tech-debt-visualizer": "dist/cli.js"
9
10
  },
10
11
  "scripts": {
11
12
  "build": "tsc",