@vibecodeqa/cli 0.35.4 → 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 +85 -5
  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,22 +200,96 @@ 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 ──
253
+ /** Load last scan from .vibe-check/report.json for instant display on startup. */
254
+ function loadCachedScan(cwd) {
255
+ try {
256
+ const reportPath = join(cwd, ".vibe-check", "report.json");
257
+ if (!existsSync(reportPath))
258
+ return null;
259
+ const report = JSON.parse(readFileSync(reportPath, "utf-8"));
260
+ const checks = report.checks || [];
261
+ const totalIssues = checks.reduce((s, c) => s + c.issues.length, 0);
262
+ return {
263
+ checks,
264
+ score: report.score ?? 0,
265
+ grade: report.grade ?? "?",
266
+ duration: report.meta?.duration ?? 0,
267
+ totalIssues,
268
+ scanning: false,
269
+ scanCount: 0,
270
+ scores: [report.score ?? 0],
271
+ };
272
+ }
273
+ catch {
274
+ return null;
275
+ }
276
+ }
203
277
  function MonitorApp({ cwd }) {
204
278
  const { exit } = useApp();
205
279
  const { stdout } = useStdout();
206
280
  const rows = stdout?.rows ?? 30;
281
+ const cached = loadCachedScan(cwd);
207
282
  const [monCfg, setMonCfg] = useState(() => loadMonitorConfig(cwd));
208
283
  const [mode, setMode] = useState("monitor");
209
- const [state, setState] = useState({
284
+ const [state, setState] = useState(cached ?? {
210
285
  checks: [], score: 0, grade: "?", duration: 0,
211
286
  totalIssues: 0, scanning: true, scanCount: 0, scores: [],
212
287
  });
213
288
  const [log, setLog] = useState([
214
- { time: ts(), text: `Monitoring ${basename(cwd)}...`, type: "info" },
289
+ { time: ts(), text: cached ? `Loaded cached scan: ${cached.grade} ${cached.score}/100` : `Monitoring ${basename(cwd)}...`, type: cached ? "scan" : "info" },
215
290
  ]);
216
291
  const scanningRef = useRef(false);
217
- const prevScoreRef = useRef(null);
292
+ const prevScoreRef = useRef(cached ? cached.score : null);
218
293
  const addLog = useCallback((text, type = "info") => {
219
294
  setLog((prev) => [...prev.slice(-50), { time: ts(), text, type }]);
220
295
  }, []);
@@ -283,7 +358,7 @@ function MonitorApp({ cwd }) {
283
358
  // Keyboard — all input handled, nothing echoed
284
359
  useInput((input, key) => {
285
360
  if (key.escape) {
286
- if (mode === "config") {
361
+ if (mode !== "monitor") {
287
362
  setMode("monitor");
288
363
  return;
289
364
  }
@@ -300,9 +375,14 @@ function MonitorApp({ cwd }) {
300
375
  doScan();
301
376
  if (input === "c")
302
377
  setMode("config");
378
+ if (input === "t")
379
+ setMode("trends");
303
380
  });
304
381
  const proj = basename(cwd);
305
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
+ }
306
386
  if (mode === "config") {
307
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") }) }));
308
388
  }
@@ -317,7 +397,7 @@ function MonitorApp({ cwd }) {
317
397
  const errorCount = state.checks.reduce((s, c) => s + c.issues.filter((i) => i.severity === "error").length, 0);
318
398
  const warnCount = state.checks.reduce((s, c) => s + c.issues.filter((i) => i.severity === "warning").length, 0);
319
399
  const activeCount = state.checks.filter((c) => !c.details.skipped && !c.details.comingSoon).length;
320
- 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] }))] })] }));
321
401
  }
322
402
  // ── Entry ──
323
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.4",
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": {