@vibecodeqa/cli 0.9.0 → 0.10.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/README.md +10 -10
- package/dist/cli.js +19 -3
- package/dist/report/html.js +13 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
#
|
|
1
|
+
# VibeCode QA
|
|
2
2
|
|
|
3
3
|
**Code health scanner for the AI coding era.**
|
|
4
4
|
|
|
5
5
|
One command. 15 checks. Full report. Zero config.
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npx
|
|
8
|
+
npx @vibecodeqa/cli
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
  
|
|
12
12
|
|
|
13
13
|
## What it does
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
vcqa scans your TypeScript/JavaScript codebase and produces a scored health report with actionable findings. It auto-detects your stack (React, Vite, vitest, Biome, etc.) and runs 15 checks across 6 categories.
|
|
16
16
|
|
|
17
17
|
The output is a self-contained HTML report with radar charts, architecture diagrams, file heatmaps, and drill-down issue lists — all navigable via sidebar and tab navigation.
|
|
18
18
|
|
|
@@ -20,19 +20,19 @@ The output is a self-contained HTML report with radar charts, architecture diagr
|
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
22
|
# Scan current directory (runs tests + coverage)
|
|
23
|
-
npx
|
|
23
|
+
npx @vibecodeqa/cli
|
|
24
24
|
|
|
25
25
|
# Fast mode (skip test execution)
|
|
26
|
-
npx
|
|
26
|
+
npx @vibecodeqa/cli --skip-tests
|
|
27
27
|
|
|
28
28
|
# CI mode (exit code 1 if score < 60)
|
|
29
|
-
npx
|
|
29
|
+
npx @vibecodeqa/cli --ci
|
|
30
30
|
|
|
31
31
|
# JSON output (pipe to other tools)
|
|
32
|
-
npx
|
|
32
|
+
npx @vibecodeqa/cli --json
|
|
33
33
|
|
|
34
34
|
# Scan a specific directory
|
|
35
|
-
npx
|
|
35
|
+
npx @vibecodeqa/cli /path/to/project
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
Output goes to `.vibe-check/`:
|
|
@@ -123,7 +123,7 @@ Each check produces a score from 0-100. The composite score is a weighted averag
|
|
|
123
123
|
|
|
124
124
|
## Trend tracking
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
vcqa reads the previous `.vibe-check/report.json` on each run and shows:
|
|
127
127
|
- Score change (↑ improved / ↓ declined)
|
|
128
128
|
- Per-check deltas
|
|
129
129
|
- New vs. fixed issue counts
|
|
@@ -170,5 +170,5 @@ MIT — Free forever as a CLI tool.
|
|
|
170
170
|
## Links
|
|
171
171
|
|
|
172
172
|
- **GitHub:** https://github.com/freeappstore-online/vibe-check
|
|
173
|
-
- **Website:** https://
|
|
173
|
+
- **Website:** https://vibecodeqa.online
|
|
174
174
|
- **Issues:** https://github.com/freeappstore-online/vibe-check/issues
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/** vibe-check — code health scanner for the AI coding era. */
|
|
3
|
-
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { existsSync, mkdirSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
5
|
import { detectRepoUrl, detectStack } from "./detect.js";
|
|
6
6
|
import { generateHTML } from "./report/html.js";
|
|
@@ -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.10.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("--")) || ".");
|
|
@@ -41,7 +41,7 @@ async function main() {
|
|
|
41
41
|
const start = Date.now();
|
|
42
42
|
if (!jsonOnly) {
|
|
43
43
|
console.log("");
|
|
44
|
-
console.log(" \x1b[
|
|
44
|
+
console.log(" \x1b[1m\x1b[38;5;141mvcqa\x1b[0m v" + VERSION);
|
|
45
45
|
console.log(" \x1b[2m" + cwd + "\x1b[0m");
|
|
46
46
|
console.log("");
|
|
47
47
|
}
|
|
@@ -106,6 +106,22 @@ async function main() {
|
|
|
106
106
|
const trend = computeTrend(report, outputDir);
|
|
107
107
|
if (!existsSync(outputDir))
|
|
108
108
|
mkdirSync(outputDir, { recursive: true });
|
|
109
|
+
// Save to history before overwriting current report
|
|
110
|
+
const historyDir = join(outputDir, "history");
|
|
111
|
+
if (!existsSync(historyDir))
|
|
112
|
+
mkdirSync(historyDir, { recursive: true });
|
|
113
|
+
const historyFile = join(historyDir, `${report.timestamp.replace(/[:.]/g, "-")}.json`);
|
|
114
|
+
writeFileSync(historyFile, JSON.stringify(report, null, 2));
|
|
115
|
+
// Keep only last 30 history entries
|
|
116
|
+
const historyFiles = readdirSync(historyDir).filter((f) => f.endsWith(".json")).sort();
|
|
117
|
+
if (historyFiles.length > 30) {
|
|
118
|
+
for (const old of historyFiles.slice(0, historyFiles.length - 30)) {
|
|
119
|
+
try {
|
|
120
|
+
unlinkSync(join(historyDir, old));
|
|
121
|
+
}
|
|
122
|
+
catch { /* ignore */ }
|
|
123
|
+
}
|
|
124
|
+
}
|
|
109
125
|
writeFileSync(join(outputDir, "report.json"), JSON.stringify(report, null, 2));
|
|
110
126
|
writeFileSync(join(outputDir, "report.html"), generateHTML(report));
|
|
111
127
|
if (jsonOnly) {
|
package/dist/report/html.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* All in one self-contained HTML file using hash routing + show/hide.
|
|
10
10
|
*/
|
|
11
11
|
import { getCheckMeta } from "../check-meta.js";
|
|
12
|
+
import { generateArchSVG } from "../runners/architecture.js";
|
|
12
13
|
function e(s) {
|
|
13
14
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
14
15
|
}
|
|
@@ -116,7 +117,7 @@ export function generateHTML(report) {
|
|
|
116
117
|
const subPages = cs.checks.map((c, i) => {
|
|
117
118
|
const meta = getCheckMeta(c.name);
|
|
118
119
|
const sk = c.details.skipped;
|
|
119
|
-
const
|
|
120
|
+
const detailsFiltered = Object.entries(c.details).filter(([k]) => k !== "skipped" && k !== "reason" && k !== "graph").map(([k, v]) => {
|
|
120
121
|
const d = Array.isArray(v) ? v.join(", ") : typeof v === "object" ? JSON.stringify(v) : String(v);
|
|
121
122
|
return `<div class="kv"><span class="k">${e(k)}</span><span class="v">${e(d)}</span></div>`;
|
|
122
123
|
}).join("");
|
|
@@ -138,7 +139,8 @@ export function generateHTML(report) {
|
|
|
138
139
|
for (const [file, issues] of byFile) {
|
|
139
140
|
issuesHtml += `<div class="fg"><div class="fn">${fl(file)} <span class="fc">${issues.length}</span></div>`;
|
|
140
141
|
for (const iss of issues) {
|
|
141
|
-
|
|
142
|
+
const prompt = `Fix this issue in ${file}${iss.line ? ":" + iss.line : ""}\\n${iss.severity}: ${iss.message}${iss.rule ? " (" + iss.rule + ")" : ""}\\nCheck: ${c.name}`;
|
|
143
|
+
issuesHtml += `<div class="ir ${iss.severity}"><span class="is">${iss.severity[0].toUpperCase()}</span>${iss.line ? `<span class="il">${iss.line}</span>` : ""}<span class="im">${e(iss.message)}</span>${iss.rule ? `<span class="iru">${e(iss.rule)}</span>` : ""}<button class="cp-btn" onclick="navigator.clipboard.writeText('${prompt.replace(/'/g, "\\'")}');this.textContent='✓';setTimeout(()=>this.textContent='📋',1000)" title="Copy fix prompt">📋</button></div>`;
|
|
142
144
|
}
|
|
143
145
|
issuesHtml += `</div>`;
|
|
144
146
|
}
|
|
@@ -153,7 +155,8 @@ export function generateHTML(report) {
|
|
|
153
155
|
<div class="ch-head"><span class="ch-g" style="color:${sk ? "#555" : gc(c.grade)}">${sk ? "—" : c.grade}</span><div><b>${e(meta.label)}</b><span class="ch-s">${sk ? "skipped" : c.score + "/100"} · weight ${meta.weight}% · ${c.duration}ms · ${c.issues.length} issues</span></div><span class="pri" style="color:${pc(meta.priority)}">${meta.priority}</span></div>
|
|
154
156
|
${meta.description ? `<div class="info-panel"><div class="ip-row"><span class="ip-label">What</span><span>${e(meta.description)}</span></div><div class="ip-row"><span class="ip-label">Risk</span><span>${e(meta.risk)}</span></div><div class="ip-row"><span class="ip-label">Fix</span><span>${e(meta.recommendation)}</span></div></div>` : ""}
|
|
155
157
|
${sk ? `<p class="skip-r">${e(c.details.reason || "skipped")}</p>` : ""}
|
|
156
|
-
${
|
|
158
|
+
${c.name === "architecture" && !sk ? `<div class="arch-svg">${generateArchSVG(c.details)}</div>` : ""}
|
|
159
|
+
${detailsFiltered ? `<div class="kvs">${detailsFiltered}</div>` : ""}
|
|
157
160
|
${issuesHtml ? `<div class="iss-list">${issuesHtml}</div>` : '<p style="color:var(--muted);font-size:0.8rem;margin-top:1rem">No issues found.</p>'}
|
|
158
161
|
</div>`;
|
|
159
162
|
}).join("");
|
|
@@ -192,7 +195,7 @@ ${fileRows || '<p style="color:var(--muted)">No file-level issues found.</p>'}
|
|
|
192
195
|
<head>
|
|
193
196
|
<meta charset="utf-8">
|
|
194
197
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
195
|
-
<title>
|
|
198
|
+
<title>VibeCode QA — ${e(proj)}</title>
|
|
196
199
|
<style>
|
|
197
200
|
:root{--bg:#09090b;--card:#111115;--border:#1e1e24;--text:#e5e5e5;--muted:#6b7280;--pass:#22c55e;--fail:#ef4444;--warn:#eab308;--info:#6366f1;--accent:#818cf8}
|
|
198
201
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
@@ -312,13 +315,17 @@ h3{font-size:0.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:
|
|
|
312
315
|
.footer{text-align:center;color:var(--muted);font-size:0.58rem;margin-top:2rem;padding:0.8rem 0;border-top:1px solid var(--border)}
|
|
313
316
|
.footer a{color:var(--muted)}
|
|
314
317
|
.flink{color:var(--accent);text-decoration:none;font-family:"SF Mono",monospace}.flink:hover{text-decoration:underline}
|
|
318
|
+
.arch-svg{margin:1rem 0;overflow-x:auto}
|
|
319
|
+
.arch-svg svg{border-radius:8px}
|
|
320
|
+
.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
|
+
.ir:hover .cp-btn{opacity:0.6}
|
|
315
322
|
@media(max-width:768px){.side{display:none}.content{margin-left:0;padding:1rem}.cats{grid-template-columns:1fr 1fr}.dash{flex-direction:column}}
|
|
316
323
|
</style>
|
|
317
324
|
</head>
|
|
318
325
|
<body>
|
|
319
326
|
|
|
320
327
|
<nav class="top">
|
|
321
|
-
<div class="logo"><span>
|
|
328
|
+
<div class="logo"><span>VibeCode</span> QA</div>
|
|
322
329
|
${topNav}
|
|
323
330
|
</nav>
|
|
324
331
|
|
|
@@ -338,7 +345,7 @@ h3{font-size:0.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:
|
|
|
338
345
|
${catPages}
|
|
339
346
|
${issuesPage}
|
|
340
347
|
${filesPage}
|
|
341
|
-
<div class="footer">Generated by <a href="https://
|
|
348
|
+
<div class="footer">Generated by <a href="https://vibecodeqa.online">VibeCode QA</a> v${report.version} — <code>npx @vibecodeqa/cli</code></div>
|
|
342
349
|
</div>
|
|
343
350
|
|
|
344
351
|
<script>
|