@vibecodeqa/cli 0.35.5 → 0.35.6

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.
Files changed (2) hide show
  1. package/dist/monitor.js +57 -2
  2. package/package.json +1 -1
package/dist/monitor.js CHANGED
@@ -13,6 +13,7 @@ import { watch, existsSync, mkdirSync, writeFileSync, readFileSync } from "node:
13
13
  import { execFile } from "node:child_process";
14
14
  import { fileURLToPath } from "node:url";
15
15
  import { detectStack, detectWorkspace } from "./detect.js";
16
+ import { loadHistory } from "./history.js";
16
17
  const VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8")).version;
17
18
  const CLI_PATH = fileURLToPath(new URL("./cli.js", import.meta.url));
18
19
  const DEFAULTS = {
@@ -199,6 +200,55 @@ function ConfigScreen({ monCfg, onSave, onClose, }) {
199
200
  return (_jsxs(Text, { children: [_jsxs(Text, { color: selected ? "white" : "gray", children: [prefix, opt.label.padEnd(28)] }), _jsxs(Text, { color: "cyan", bold: true, children: ["\u25C0 ", String(opt.value).padStart(5), " \u25B6"] })] }, opt.key));
200
201
  }), _jsx(Text, { dimColor: true, children: " " }), _jsx(Text, { dimColor: true, children: " \u2191\u2193 navigate \u00B7 Space toggle \u00B7 \u2190\u2192 adjust \u00B7 Shift+\u2190\u2192 \u00D710" }), _jsx(Text, { dimColor: true, children: " s save & close \u00B7 Esc cancel" })] }));
201
202
  }
203
+ // ── Trends Screen ──
204
+ function sparkFull(values, width) {
205
+ if (values.length < 2)
206
+ return "";
207
+ const bars = " ▁▂▃▄▅▆▇█";
208
+ const min = Math.min(...values);
209
+ const max = Math.max(...values);
210
+ const range = max - min || 1;
211
+ // Resample to fit width
212
+ const sampled = [];
213
+ for (let i = 0; i < width; i++) {
214
+ const idx = Math.round((i / (width - 1)) * (values.length - 1));
215
+ sampled.push(values[idx]);
216
+ }
217
+ return sampled.map((v) => bars[Math.round(((v - min) / range) * 8)]).join("");
218
+ }
219
+ function TrendsScreen({ cwd, height, onClose }) {
220
+ const historyDir = join(cwd, ".vibe-check", "history");
221
+ const history = loadHistory(historyDir);
222
+ useInput((input, key) => {
223
+ if (key.escape || input === "t")
224
+ onClose();
225
+ });
226
+ if (history.length < 2) {
227
+ return (_jsxs(Box, { flexDirection: "column", height: height, paddingX: 1, children: [_jsx(Text, { bold: true, color: "magenta", children: " \u25C8 Trends" }), _jsx(Text, { dimColor: true, children: " Need at least 2 scans. Run the scanner a few more times." }), _jsx(Text, { dimColor: true, children: " Esc to go back" })] }));
228
+ }
229
+ const latest = history[history.length - 1];
230
+ const first = history[0];
231
+ const overallDelta = latest.score - first.score;
232
+ const scores = history.map((h) => h.score);
233
+ const chartWidth = 40;
234
+ // Get all check names from latest
235
+ const checkNames = [...latest.checkScores.keys()];
236
+ // Build per-check trends
237
+ const checkTrends = checkNames
238
+ .map((name) => {
239
+ const values = history.map((h) => h.checkScores.get(name) ?? 0).filter((v) => v > 0);
240
+ if (values.length < 2)
241
+ return null;
242
+ const current = values[values.length - 1];
243
+ const prev = values[0];
244
+ const delta = current - prev;
245
+ return { name, current, delta, spark: sparkFull(values, 20) };
246
+ })
247
+ .filter(Boolean)
248
+ .sort((a, b) => a.delta - b.delta);
249
+ const visibleChecks = Math.max(1, height - 10);
250
+ return (_jsxs(Box, { flexDirection: "column", height: height, paddingX: 1, children: [_jsxs(Text, { bold: true, color: "magenta", children: [" \u25C8 Trends \u2014 ", history.length, " scans"] }), _jsxs(Text, { dimColor: true, children: [" ", first.timestamp.split("T")[0], " \u2192 ", latest.timestamp.split("T")[0]] }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: " Overall Score" }), _jsxs(Text, { children: [_jsxs(Text, { color: "cyan", children: [" ", sparkFull(scores, chartWidth), " "] }), _jsxs(Text, { color: gc(latest.score >= 90 ? "A" : latest.score >= 75 ? "B" : "C"), bold: true, children: [" ", latest.score] }), _jsxs(Text, { color: overallDelta >= 0 ? "green" : "red", children: [" ", overallDelta >= 0 ? "+" : "", overallDelta] })] }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: " Per-Check (first \u2192 latest)" }), checkTrends.slice(0, visibleChecks).map((t) => (_jsxs(Text, { children: [_jsxs(Text, { children: [" ", t.name.slice(0, 14).padEnd(14), " "] }), _jsxs(Text, { color: "cyan", children: [t.spark, " "] }), _jsxs(Text, { color: gc(t.current >= 90 ? "A" : t.current >= 75 ? "B" : "C"), children: [String(t.current).padStart(3), " "] }), _jsxs(Text, { color: t.delta >= 0 ? "green" : "red", children: [t.delta >= 0 ? "+" : "", t.delta] })] }, t.name))), checkTrends.length > visibleChecks && _jsxs(Text, { dimColor: true, children: [" +", checkTrends.length - visibleChecks, " more"] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: " Esc back to dashboard" }) })] }));
251
+ }
202
252
  // ── Main App ──
203
253
  /** Load last scan from .vibe-check/report.json for instant display on startup. */
204
254
  function loadCachedScan(cwd) {
@@ -308,7 +358,7 @@ function MonitorApp({ cwd }) {
308
358
  // Keyboard — all input handled, nothing echoed
309
359
  useInput((input, key) => {
310
360
  if (key.escape) {
311
- if (mode === "config") {
361
+ if (mode !== "monitor") {
312
362
  setMode("monitor");
313
363
  return;
314
364
  }
@@ -325,9 +375,14 @@ function MonitorApp({ cwd }) {
325
375
  doScan();
326
376
  if (input === "c")
327
377
  setMode("config");
378
+ if (input === "t")
379
+ setMode("trends");
328
380
  });
329
381
  const proj = basename(cwd);
330
382
  const p = monCfg.panels;
383
+ if (mode === "trends") {
384
+ return (_jsx(Box, { flexDirection: "column", height: rows, children: _jsx(TrendsScreen, { cwd: cwd, height: rows, onClose: () => setMode("monitor") }) }));
385
+ }
331
386
  if (mode === "config") {
332
387
  return (_jsx(Box, { flexDirection: "column", height: rows, justifyContent: "center", alignItems: "center", children: _jsx(ConfigScreen, { monCfg: monCfg, onSave: (cfg) => { setMonCfg(cfg); saveMonitorConfig(cwd, cfg); addLog("Settings saved", "info"); }, onClose: () => setMode("monitor") }) }));
333
388
  }
@@ -342,7 +397,7 @@ function MonitorApp({ cwd }) {
342
397
  const errorCount = state.checks.reduce((s, c) => s + c.issues.filter((i) => i.severity === "error").length, 0);
343
398
  const warnCount = state.checks.reduce((s, c) => s + c.issues.filter((i) => i.severity === "warning").length, 0);
344
399
  const activeCount = state.checks.filter((c) => !c.details.skipped && !c.details.comingSoon).length;
345
- return (_jsxs(Box, { flexDirection: "column", height: rows, children: [_jsxs(Box, { paddingX: 1, justifyContent: "space-between", children: [_jsxs(Text, { children: [_jsx(Text, { color: "magenta", bold: true, children: "vcqa monitor" }), _jsxs(Text, { dimColor: true, children: [" v", VERSION] })] }), _jsxs(Text, { children: [_jsx(Text, { bold: true, children: proj }), _jsxs(Text, { dimColor: true, children: [" ", stack.language, "/", stack.framework, workspace.isMonorepo ? ` · ${workspace.tool}` : ""] })] }), _jsxs(Text, { children: [state.score > 0 && _jsxs(Text, { color: gc(state.grade), bold: true, children: [state.grade, " ", state.score, " "] }), _jsx(Text, { dimColor: true, children: state.scanning ? "⟳ scanning" : "● watching" })] })] }), _jsx(Box, { paddingX: 1, gap: 2, height: 1, children: state.scanCount > 0 && (_jsxs(_Fragment, { children: [_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "E " }), _jsx(Text, { color: "red", bold: true, children: errorCount })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "W " }), _jsx(Text, { color: "yellow", bold: true, children: warnCount })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "checks " }), _jsx(Text, { bold: true, children: activeCount })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "scan " }), _jsxs(Text, { children: [state.duration, "ms"] })] }), state.scores.length >= 2 && _jsx(Text, { color: "cyan", children: spark(state.scores) })] })) }), _jsxs(Box, { height: bodyRows, children: [sidebarVisible && (_jsxs(Box, { flexDirection: "column", width: 26, children: [p.score && _jsx(ScorePanel, { state: state, height: scoreH }), p.checks && _jsx(ChecksPanel, { checks: state.checks, height: checksH })] })), mainVisible && (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [p.activity && _jsx(ActivityPanel, { log: log, height: activityH }), p.issues && _jsx(IssuesPanel, { checks: state.checks, height: issuesH })] })), !sidebarVisible && !mainVisible && (_jsx(Box, { height: bodyRows, justifyContent: "center", alignItems: "center", children: _jsx(Text, { dimColor: true, children: "All panels hidden. Press c to configure." }) }))] }), _jsxs(Box, { paddingX: 1, justifyContent: "space-between", children: [_jsx(Text, { dimColor: true, children: "q/Esc quit \u00B7 r rescan \u00B7 c settings" }), monCfg.alertBelow > 0 && (_jsxs(Text, { dimColor: true, children: ["alert <", monCfg.alertBelow, " \u00B7 drop \u2265", monCfg.alertDrop] }))] })] }));
400
+ return (_jsxs(Box, { flexDirection: "column", height: rows, children: [_jsxs(Box, { paddingX: 1, justifyContent: "space-between", children: [_jsxs(Text, { children: [_jsx(Text, { color: "magenta", bold: true, children: "vcqa monitor" }), _jsxs(Text, { dimColor: true, children: [" v", VERSION] })] }), _jsxs(Text, { children: [_jsx(Text, { bold: true, children: proj }), _jsxs(Text, { dimColor: true, children: [" ", stack.language, "/", stack.framework, workspace.isMonorepo ? ` · ${workspace.tool}` : ""] })] }), _jsxs(Text, { children: [state.score > 0 && _jsxs(Text, { color: gc(state.grade), bold: true, children: [state.grade, " ", state.score, " "] }), _jsx(Text, { dimColor: true, children: state.scanning ? "⟳ scanning" : "● watching" })] })] }), _jsx(Box, { paddingX: 1, gap: 2, height: 1, children: state.scanCount > 0 && (_jsxs(_Fragment, { children: [_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "E " }), _jsx(Text, { color: "red", bold: true, children: errorCount })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "W " }), _jsx(Text, { color: "yellow", bold: true, children: warnCount })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "checks " }), _jsx(Text, { bold: true, children: activeCount })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "scan " }), _jsxs(Text, { children: [state.duration, "ms"] })] }), state.scores.length >= 2 && _jsx(Text, { color: "cyan", children: spark(state.scores) })] })) }), _jsxs(Box, { height: bodyRows, children: [sidebarVisible && (_jsxs(Box, { flexDirection: "column", width: 26, children: [p.score && _jsx(ScorePanel, { state: state, height: scoreH }), p.checks && _jsx(ChecksPanel, { checks: state.checks, height: checksH })] })), mainVisible && (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [p.activity && _jsx(ActivityPanel, { log: log, height: activityH }), p.issues && _jsx(IssuesPanel, { checks: state.checks, height: issuesH })] })), !sidebarVisible && !mainVisible && (_jsx(Box, { height: bodyRows, justifyContent: "center", alignItems: "center", children: _jsx(Text, { dimColor: true, children: "All panels hidden. Press c to configure." }) }))] }), _jsxs(Box, { paddingX: 1, justifyContent: "space-between", children: [_jsx(Text, { dimColor: true, children: "q/Esc quit \u00B7 r rescan \u00B7 t trends \u00B7 c settings" }), monCfg.alertBelow > 0 && (_jsxs(Text, { dimColor: true, children: ["alert <", monCfg.alertBelow, " \u00B7 drop \u2265", monCfg.alertDrop] }))] })] }));
346
401
  }
347
402
  // ── Entry ──
348
403
  export async function startMonitor(cwd) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibecodeqa/cli",
3
- "version": "0.35.5",
3
+ "version": "0.35.6",
4
4
  "description": "Code health scanner for the AI coding era. 25 checks, zero config, full report.",
5
5
  "type": "module",
6
6
  "bin": {