@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.
- package/dist/monitor.js +85 -5
- 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
|
|
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) {
|