@vibecodeqa/cli 0.10.0 → 0.11.0
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/dist/cli.js +29 -2
- package/dist/report/html.js +29 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -22,7 +22,7 @@ import { runTypeSafety } from "./runners/type-safety.js";
|
|
|
22
22
|
import { computeScore } from "./score.js";
|
|
23
23
|
import { computeTrend, formatTrend } from "./trend.js";
|
|
24
24
|
import { gradeFromScore } from "./types.js";
|
|
25
|
-
const VERSION = "0.
|
|
25
|
+
const VERSION = "0.11.0";
|
|
26
26
|
const args = process.argv.slice(2);
|
|
27
27
|
const flags = new Set(args.filter((a) => a.startsWith("--")));
|
|
28
28
|
const cwd = resolve(args.find((a) => !a.startsWith("--")) || ".");
|
|
@@ -30,6 +30,7 @@ const outputDir = join(cwd, ".vibe-check");
|
|
|
30
30
|
const jsonOnly = flags.has("--json");
|
|
31
31
|
const ciMode = flags.has("--ci");
|
|
32
32
|
const skipTests = flags.has("--skip-tests");
|
|
33
|
+
const watchMode = flags.has("--watch");
|
|
33
34
|
function color(grade) {
|
|
34
35
|
if (grade === "A")
|
|
35
36
|
return "\x1b[32m";
|
|
@@ -141,7 +142,7 @@ async function main() {
|
|
|
141
142
|
if (ciMode && score < 60) {
|
|
142
143
|
process.exit(1);
|
|
143
144
|
}
|
|
144
|
-
if (!jsonOnly && !ciMode) {
|
|
145
|
+
if (!jsonOnly && !ciMode && !watchMode) {
|
|
145
146
|
try {
|
|
146
147
|
const { execSync } = await import("node:child_process");
|
|
147
148
|
const openCmd = process.platform === "darwin" ? "open" : "xdg-open";
|
|
@@ -149,6 +150,32 @@ async function main() {
|
|
|
149
150
|
}
|
|
150
151
|
catch { /* failed to open browser */ }
|
|
151
152
|
}
|
|
153
|
+
// Watch mode — re-run on file changes
|
|
154
|
+
if (watchMode) {
|
|
155
|
+
const { watch } = await import("node:fs");
|
|
156
|
+
const srcDirs = ["src", "web/src"].map((d) => join(cwd, d)).filter((d) => existsSync(d));
|
|
157
|
+
if (srcDirs.length === 0) {
|
|
158
|
+
console.log(" \x1b[31mNo src/ directory to watch\x1b[0m");
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
console.log(" \x1b[2mWatching for changes... (Ctrl+C to stop)\x1b[0m");
|
|
162
|
+
console.log("");
|
|
163
|
+
let debounce = null;
|
|
164
|
+
for (const dir of srcDirs) {
|
|
165
|
+
watch(dir, { recursive: true }, (_event, filename) => {
|
|
166
|
+
if (!filename || filename.includes("node_modules") || filename.includes(".vibe-check"))
|
|
167
|
+
return;
|
|
168
|
+
if (debounce)
|
|
169
|
+
clearTimeout(debounce);
|
|
170
|
+
debounce = setTimeout(() => {
|
|
171
|
+
console.log(` \x1b[2mChanged: ${filename} — re-scanning...\x1b[0m`);
|
|
172
|
+
main().catch(() => { });
|
|
173
|
+
}, 500);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
// Keep process alive
|
|
177
|
+
await new Promise(() => { });
|
|
178
|
+
}
|
|
152
179
|
}
|
|
153
180
|
main().catch((err) => {
|
|
154
181
|
console.error("vibe-check error:", err);
|
package/dist/report/html.js
CHANGED
|
@@ -78,6 +78,7 @@ export function generateHTML(report) {
|
|
|
78
78
|
...GROUPS.map((g) => ({ id: g.id, label: g.label })),
|
|
79
79
|
{ id: "issues", label: `Issues (${totalIssues})` },
|
|
80
80
|
{ id: "files", label: "File Map" },
|
|
81
|
+
{ id: "heatmap", label: "Heatmap" },
|
|
81
82
|
];
|
|
82
83
|
const topNav = topNavItems.map((t) => `<a class="tn" data-page="${t.id}" onclick="go('${t.id}')">${t.label}</a>`).join("");
|
|
83
84
|
// Overview page
|
|
@@ -189,6 +190,29 @@ ${allIssues.length > 200 ? `<p style="color:var(--muted);text-align:center;margi
|
|
|
189
190
|
<h2>File Heatmap</h2>
|
|
190
191
|
<p style="color:var(--muted);font-size:0.78rem;margin-bottom:1rem">Top ${topFiles.length} files by total issues across all checks</p>
|
|
191
192
|
${fileRows || '<p style="color:var(--muted)">No file-level issues found.</p>'}
|
|
193
|
+
</div>`;
|
|
194
|
+
// Codebase heatmap — each file = row of pixels, color = issue density
|
|
195
|
+
const heatmapFiles = [...fileIssues.entries()]
|
|
196
|
+
.sort((a, b) => b[1].errors + b[1].warnings - a[1].errors - a[1].warnings)
|
|
197
|
+
.slice(0, 30);
|
|
198
|
+
let heatmapHtml = "";
|
|
199
|
+
if (heatmapFiles.length > 0) {
|
|
200
|
+
const maxIssues = Math.max(...heatmapFiles.map(([, d]) => d.errors + d.warnings));
|
|
201
|
+
heatmapHtml = heatmapFiles.map(([file, d]) => {
|
|
202
|
+
const total = d.errors + d.warnings;
|
|
203
|
+
const intensity = maxIssues > 0 ? total / maxIssues : 0;
|
|
204
|
+
const r = Math.round(239 * intensity); // red channel
|
|
205
|
+
const g = Math.round(68 * (1 - intensity) + 197 * (d.errors === 0 ? 0.3 : 0)); // green
|
|
206
|
+
const color = `rgb(${r},${g},30)`;
|
|
207
|
+
const barW = Math.max(4, Math.round(intensity * 200));
|
|
208
|
+
const checks = [...d.checks].join(", ");
|
|
209
|
+
return `<div class="hm-row"><span class="hm-name">${fl(file)}</span><div class="hm-bar" style="width:${barW}px;background:${color}" title="${total} issues (${checks})"></div><span class="hm-count">${d.errors}E ${d.warnings}W</span></div>`;
|
|
210
|
+
}).join("");
|
|
211
|
+
}
|
|
212
|
+
const heatmapPage = `<div id="p-heatmap" class="page">
|
|
213
|
+
<h2>Code Heatmap</h2>
|
|
214
|
+
<p style="color:var(--muted);font-size:0.78rem;margin-bottom:1rem">Visual density of issues per file. Red = errors, orange = warnings. Bar width = relative issue count.</p>
|
|
215
|
+
${heatmapHtml || '<p style="color:var(--muted)">No issues to visualize.</p>'}
|
|
192
216
|
</div>`;
|
|
193
217
|
return `<!DOCTYPE html>
|
|
194
218
|
<html lang="en">
|
|
@@ -316,6 +340,10 @@ h3{font-size:0.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:
|
|
|
316
340
|
.footer a{color:var(--muted)}
|
|
317
341
|
.flink{color:var(--accent);text-decoration:none;font-family:"SF Mono",monospace}.flink:hover{text-decoration:underline}
|
|
318
342
|
.arch-svg{margin:1rem 0;overflow-x:auto}
|
|
343
|
+
.hm-row{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.2rem;font-size:0.7rem}
|
|
344
|
+
.hm-name{width:200px;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:"SF Mono",monospace;font-size:0.65rem}
|
|
345
|
+
.hm-bar{height:14px;border-radius:3px;min-width:4px}
|
|
346
|
+
.hm-count{color:var(--muted);font-size:0.65rem;flex-shrink:0}
|
|
319
347
|
.arch-svg svg{border-radius:8px}
|
|
320
348
|
.cp-btn{background:none;border:none;cursor:pointer;font-size:0.6rem;opacity:0.3;padding:0 0.2rem;flex-shrink:0}.cp-btn:hover{opacity:1}
|
|
321
349
|
.ir:hover .cp-btn{opacity:0.6}
|
|
@@ -345,6 +373,7 @@ h3{font-size:0.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:
|
|
|
345
373
|
${catPages}
|
|
346
374
|
${issuesPage}
|
|
347
375
|
${filesPage}
|
|
376
|
+
${heatmapPage}
|
|
348
377
|
<div class="footer">Generated by <a href="https://vibecodeqa.online">VibeCode QA</a> v${report.version} — <code>npx @vibecodeqa/cli</code></div>
|
|
349
378
|
</div>
|
|
350
379
|
|