@vibecodeqa/cli 0.11.0 → 0.12.1

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
@@ -25,6 +25,9 @@ npx @vibecodeqa/cli
25
25
  # Fast mode (skip test execution)
26
26
  npx @vibecodeqa/cli --skip-tests
27
27
 
28
+ # Watch mode (re-scan on file changes)
29
+ npx @vibecodeqa/cli --watch
30
+
28
31
  # CI mode (exit code 1 if score < 60)
29
32
  npx @vibecodeqa/cli --ci
30
33
 
@@ -36,8 +39,9 @@ npx @vibecodeqa/cli /path/to/project
36
39
  ```
37
40
 
38
41
  Output goes to `.vibe-check/`:
39
- - `report.html` — navigable dashboard (open in browser)
42
+ - `report.html` — navigable multi-page dashboard (open in browser)
40
43
  - `report.json` — machine-readable results
44
+ - `history/` — last 30 reports for trend tracking
41
45
 
42
46
  ## Checks
43
47
 
@@ -113,12 +117,18 @@ Each check produces a score from 0-100. The composite score is a weighted averag
113
117
 
114
118
  ## Report features
115
119
 
120
+ The report is a multi-page navigable dashboard:
121
+
122
+ - **10 pages**: Overview, Foundations, Quality, Testing, Architecture, Security, LLM Readiness, Issues, File Map, Heatmap
123
+ - **Top nav + sidebar** — navigate by category and check
116
124
  - **Radar chart** — 6-axis view of category scores
117
- - **Architecture diagram** — SVG showing module relationships and import edges
118
- - **Trend comparison** — score delta vs. previous run
119
- - **File heatmap** — top files by issue count across all checks
120
- - **GitHub links** — click any file:line to open in GitHub
121
- - **Info panels** — each check explains what it measures, why it matters, and how to fix issues
125
+ - **Architecture SVG diagram** — modules grouped by directory, import edges, node size by fan-in
126
+ - **Code heatmap** — colored bars showing issue density per file
127
+ - **Trend comparison** — score delta vs. previous run (reads previous report.json)
128
+ - **File map** — top files by issue count across all checks
129
+ - **GitHub links** — click any file:line to open in GitHub (auto-detected from git remote)
130
+ - **Actionable prompts** — 📋 button on every issue copies a fix prompt for Claude/Codex
131
+ - **Info panels** — each check has What/Risk/Fix explanations with research citations
122
132
  - **Priority badges** — critical/high/medium/low on each check
123
133
 
124
134
  ## Trend tracking
@@ -133,6 +143,7 @@ vcqa reads the previous `.vibe-check/report.json` on each run and shows:
133
143
  | Flag | Description |
134
144
  |------|-------------|
135
145
  | `--skip-tests` | Skip test execution and coverage (fast mode) |
146
+ | `--watch` | Re-scan automatically on file changes |
136
147
  | `--ci` | Exit code 1 if composite score < 60 |
137
148
  | `--json` | Output JSON to stdout (no HTML, no browser) |
138
149
 
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, readdirSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { existsSync, mkdirSync, readFileSync, 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,8 @@ 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.11.0";
25
+ const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
26
+ const VERSION = pkg.version;
26
27
  const args = process.argv.slice(2);
27
28
  const flags = new Set(args.filter((a) => a.startsWith("--")));
28
29
  const cwd = resolve(args.find((a) => !a.startsWith("--")) || ".");
@@ -140,8 +140,9 @@ export function generateHTML(report) {
140
140
  for (const [file, issues] of byFile) {
141
141
  issuesHtml += `<div class="fg"><div class="fn">${fl(file)} <span class="fc">${issues.length}</span></div>`;
142
142
  for (const iss of issues) {
143
- const prompt = `Fix this issue in ${file}${iss.line ? ":" + iss.line : ""}\\n${iss.severity}: ${iss.message}${iss.rule ? " (" + iss.rule + ")" : ""}\\nCheck: ${c.name}`;
144
- 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>`;
143
+ const promptText = `Fix this issue in ${e(file)}${iss.line ? ":" + iss.line : ""}\n${iss.severity}: ${e(iss.message)}${iss.rule ? " (" + e(iss.rule) + ")" : ""}\nCheck: ${e(c.name)}`;
144
+ const safePrompt = promptText.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n");
145
+ 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('${safePrompt}');this.textContent='✓';setTimeout(()=>this.textContent='📋',1000)" title="Copy fix prompt">📋</button></div>`;
145
146
  }
146
147
  issuesHtml += `</div>`;
147
148
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibecodeqa/cli",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "description": "Code health scanner for the AI coding era. 15 checks, zero config, full report.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,3 +0,0 @@
1
- /** Coverage runner — runs tests with coverage and parses the summary. */
2
- import type { CheckResult, StackInfo } from "../types.js";
3
- export declare function runCoverage(cwd: string, stack: StackInfo): CheckResult;
@@ -1,65 +0,0 @@
1
- /** Coverage runner — runs tests with coverage and parses the summary. */
2
- import { existsSync, readFileSync } from "node:fs";
3
- import { join } from "node:path";
4
- import { gradeFromScore } from "../types.js";
5
- import { run } from "./exec.js";
6
- export function runCoverage(cwd, stack) {
7
- const start = Date.now();
8
- if (stack.testRunner === "none") {
9
- return {
10
- name: "coverage",
11
- score: 0,
12
- grade: "F",
13
- details: { skipped: true, reason: "no test runner" },
14
- issues: [],
15
- duration: Date.now() - start,
16
- };
17
- }
18
- // Run tests with coverage
19
- const cmd = stack.testRunner === "vitest"
20
- ? "npx vitest run --coverage 2>/dev/null || true"
21
- : "npx jest --coverage --coverageReporters=json-summary 2>/dev/null || true";
22
- run(cmd, cwd, 120_000);
23
- // Look for coverage summary
24
- const searchPaths = [
25
- "coverage/coverage-summary.json",
26
- "test-results/coverage/coverage-summary.json",
27
- ];
28
- let summary = null;
29
- for (const p of searchPaths) {
30
- const full = join(cwd, p);
31
- if (existsSync(full)) {
32
- try {
33
- summary = JSON.parse(readFileSync(full, "utf-8"));
34
- break;
35
- }
36
- catch {
37
- /* parse failed */
38
- }
39
- }
40
- }
41
- if (!summary?.total) {
42
- return {
43
- name: "coverage",
44
- score: 0,
45
- grade: "F",
46
- details: { skipped: true, reason: "no coverage data generated" },
47
- issues: [],
48
- duration: Date.now() - start,
49
- };
50
- }
51
- const stmts = summary.total.statements?.pct || 0;
52
- const lines = summary.total.lines?.pct || 0;
53
- const branches = summary.total.branches?.pct || 0;
54
- const functions = summary.total.functions?.pct || 0;
55
- // Score is the average of all four metrics
56
- const score = Math.round((stmts + lines + branches + functions) / 4);
57
- return {
58
- name: "coverage",
59
- score,
60
- grade: gradeFromScore(score),
61
- details: { statements: stmts, lines, branches, functions },
62
- issues: [],
63
- duration: Date.now() - start,
64
- };
65
- }
@@ -1,3 +0,0 @@
1
- /** Test runner — auto-detects vitest or jest. */
2
- import type { CheckResult, StackInfo } from "../types.js";
3
- export declare function runTests(cwd: string, stack: StackInfo): CheckResult;
@@ -1,54 +0,0 @@
1
- /** Test runner — auto-detects vitest or jest. */
2
- import { gradeFromScore } from "../types.js";
3
- import { run } from "./exec.js";
4
- export function runTests(cwd, stack) {
5
- const start = Date.now();
6
- if (stack.testRunner === "none") {
7
- return {
8
- name: "tests",
9
- score: 0,
10
- grade: "F",
11
- details: { skipped: true, reason: "no test runner" },
12
- issues: [],
13
- duration: Date.now() - start,
14
- };
15
- }
16
- const cmd = stack.testRunner === "vitest"
17
- ? "npx vitest run --reporter=json 2>/dev/null || true"
18
- : "npx jest --json 2>/dev/null || true";
19
- const { stdout } = run(cmd, cwd, 120_000);
20
- // Extract JSON from output (vitest may print other stuff before the JSON)
21
- let data = null;
22
- try {
23
- // Find the JSON object in stdout
24
- const jsonStart = stdout.indexOf("{");
25
- if (jsonStart >= 0) {
26
- data = JSON.parse(stdout.slice(jsonStart));
27
- }
28
- }
29
- catch {
30
- /* parse failed */
31
- }
32
- if (!data) {
33
- return {
34
- name: "tests",
35
- score: 0,
36
- grade: "F",
37
- details: { error: "could not parse test output" },
38
- issues: [],
39
- duration: Date.now() - start,
40
- };
41
- }
42
- const passed = data.numPassedTests || 0;
43
- const failed = data.numFailedTests || 0;
44
- const total = data.numTotalTests || 0;
45
- const score = total === 0 ? 0 : Math.round((passed / total) * 100);
46
- return {
47
- name: "tests",
48
- score,
49
- grade: gradeFromScore(score),
50
- details: { passed, failed, total, runner: stack.testRunner },
51
- issues: [],
52
- duration: Date.now() - start,
53
- };
54
- }