@vibecodeqa/cli 0.9.1 → 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/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.9.1";
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("--")) || ".");
@@ -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) {
@@ -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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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 details = Object.entries(c.details).filter(([k]) => k !== "skipped" && k !== "reason").map(([k, v]) => {
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
- 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>` : ""}</div>`;
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
- ${details ? `<div class="kvs">${details}</div>` : ""}
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("");
@@ -312,6 +315,10 @@ 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>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibecodeqa/cli",
3
- "version": "0.9.1",
3
+ "version": "0.10.0",
4
4
  "description": "Code health scanner for the AI coding era. 15 checks, zero config, full report.",
5
5
  "type": "module",
6
6
  "bin": {