@vibecodeqa/cli 0.35.8 → 0.35.9
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 +90 -80
- package/package.json +1 -1
package/dist/monitor.js
CHANGED
|
@@ -6,7 +6,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
6
6
|
* Press 'c' to open settings: thresholds, panel toggles, scan options.
|
|
7
7
|
* Config persists to .vibe-check/monitor.json.
|
|
8
8
|
*/
|
|
9
|
-
import { useState, useEffect, useCallback, useRef } from "react";
|
|
9
|
+
import { useState, useEffect, useCallback, useRef, useMemo } from "react";
|
|
10
10
|
import { render, Box, Text, useApp, useInput, useStdout } from "ink";
|
|
11
11
|
import { resolve, join, basename } from "node:path";
|
|
12
12
|
import { watch, existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
|
|
@@ -129,59 +129,7 @@ function ActivityPanel({ log, height }) {
|
|
|
129
129
|
const visibleLines = Math.max(1, height - 3);
|
|
130
130
|
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, height: height, overflowY: "hidden", children: [_jsx(Text, { bold: true, color: "magenta", children: " \u25C8 Activity" }), log.slice(-visibleLines).map((entry, i) => (_jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { dimColor: true, children: [entry.time, " "] }), _jsx(Text, { color: colors[entry.type], children: entry.text })] }, i)))] }));
|
|
131
131
|
}
|
|
132
|
-
function ConfigScreen({
|
|
133
|
-
const [cursor, setCursor] = useState(0);
|
|
134
|
-
const [cfg, setCfg] = useState(JSON.parse(JSON.stringify(monCfg)));
|
|
135
|
-
const options = [
|
|
136
|
-
{ key: "alertBelow", label: "Alert when score below", type: "number", value: cfg.alertBelow, path: ["alertBelow"] },
|
|
137
|
-
{ key: "alertDrop", label: "Alert on score drop ≥", type: "number", value: cfg.alertDrop, path: ["alertDrop"] },
|
|
138
|
-
{ key: "debounceMs", label: "Scan debounce (ms)", type: "number", value: cfg.debounceMs, path: ["debounceMs"] },
|
|
139
|
-
{ key: "skipTests", label: "Skip test execution", type: "toggle", value: cfg.skipTests, path: ["skipTests"] },
|
|
140
|
-
{ key: "p-score", label: "Panel: Score", type: "toggle", value: cfg.panels.score, path: ["panels", "score"] },
|
|
141
|
-
{ key: "p-checks", label: "Panel: Checks", type: "toggle", value: cfg.panels.checks, path: ["panels", "checks"] },
|
|
142
|
-
{ key: "p-activity", label: "Panel: Activity", type: "toggle", value: cfg.panels.activity, path: ["panels", "activity"] },
|
|
143
|
-
{ key: "p-issues", label: "Panel: Issues", type: "toggle", value: cfg.panels.issues, path: ["panels", "issues"] },
|
|
144
|
-
];
|
|
145
|
-
useInput((input, key) => {
|
|
146
|
-
if (key.escape || input === "c") {
|
|
147
|
-
onClose();
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
if (key.upArrow)
|
|
151
|
-
setCursor((c) => Math.max(0, c - 1));
|
|
152
|
-
if (key.downArrow)
|
|
153
|
-
setCursor((c) => Math.min(options.length - 1, c + 1));
|
|
154
|
-
const opt = options[cursor];
|
|
155
|
-
if (!opt)
|
|
156
|
-
return;
|
|
157
|
-
if (opt.type === "toggle" && (input === " " || key.return)) {
|
|
158
|
-
const next = { ...cfg };
|
|
159
|
-
if (opt.path.length === 1) {
|
|
160
|
-
next[opt.path[0]] = !opt.value;
|
|
161
|
-
}
|
|
162
|
-
else {
|
|
163
|
-
next.panels[opt.path[1]] = !opt.value;
|
|
164
|
-
}
|
|
165
|
-
setCfg(next);
|
|
166
|
-
}
|
|
167
|
-
if (opt.type === "number") {
|
|
168
|
-
const step = key.shift ? 10 : 1;
|
|
169
|
-
let v = opt.value;
|
|
170
|
-
if (key.rightArrow)
|
|
171
|
-
v += step;
|
|
172
|
-
if (key.leftArrow)
|
|
173
|
-
v = Math.max(0, v - step);
|
|
174
|
-
if (v !== opt.value) {
|
|
175
|
-
const next = { ...cfg };
|
|
176
|
-
next[opt.path[0]] = v;
|
|
177
|
-
setCfg(next);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
if (input === "s") {
|
|
181
|
-
onSave(cfg);
|
|
182
|
-
onClose();
|
|
183
|
-
}
|
|
184
|
-
});
|
|
132
|
+
function ConfigScreen({ cursor, options }) {
|
|
185
133
|
return (_jsxs(Box, { flexDirection: "column", borderStyle: "double", borderColor: "magenta", paddingX: 2, paddingY: 1, children: [_jsx(Text, { bold: true, color: "magenta", children: " \u2699 Settings" }), _jsx(Text, { dimColor: true, children: " " }), options.map((opt, i) => {
|
|
186
134
|
const selected = i === cursor;
|
|
187
135
|
const prefix = selected ? "▸ " : " ";
|
|
@@ -208,13 +156,9 @@ function sparkFull(values, width) {
|
|
|
208
156
|
}
|
|
209
157
|
return sampled.map((v) => bars[Math.round(((v - min) / range) * 8)]).join("");
|
|
210
158
|
}
|
|
211
|
-
function TrendsScreen({ cwd, height
|
|
159
|
+
function TrendsScreen({ cwd, height }) {
|
|
212
160
|
const historyDir = join(cwd, ".vibe-check", "history");
|
|
213
161
|
const history = loadHistory(historyDir);
|
|
214
|
-
useInput((input, key) => {
|
|
215
|
-
if (key.escape || input === "t")
|
|
216
|
-
onClose();
|
|
217
|
-
});
|
|
218
162
|
if (history.length < 2) {
|
|
219
163
|
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" })] }));
|
|
220
164
|
}
|
|
@@ -312,11 +256,16 @@ function MonitorApp({ cwd }) {
|
|
|
312
256
|
const { exit } = useApp();
|
|
313
257
|
const { stdout } = useStdout();
|
|
314
258
|
const rows = stdout?.rows ?? 30;
|
|
315
|
-
|
|
259
|
+
// Memoize filesystem I/O — only run once
|
|
260
|
+
const cached = useMemo(() => loadCachedScan(cwd), [cwd]);
|
|
261
|
+
const workspace = useMemo(() => detectWorkspace(cwd), [cwd]);
|
|
262
|
+
const stack = useMemo(() => detectStack(cwd, workspace), [cwd, workspace]);
|
|
316
263
|
const [monCfg, setMonCfg] = useState(() => loadMonitorConfig(cwd));
|
|
317
264
|
const [mode, setMode] = useState({ view: "dashboard" });
|
|
318
265
|
const [panel, setPanel] = useState("checks");
|
|
319
266
|
const [cursor, setCursor] = useState(0);
|
|
267
|
+
const [configCursor, setConfigCursor] = useState(0);
|
|
268
|
+
const [pendingCfg, setPendingCfg] = useState(null);
|
|
320
269
|
const [state, setState] = useState(cached ?? {
|
|
321
270
|
checks: [], score: 0, grade: "?", duration: 0,
|
|
322
271
|
totalIssues: 0, scanning: true, scanCount: 0, scores: [],
|
|
@@ -330,17 +279,19 @@ function MonitorApp({ cwd }) {
|
|
|
330
279
|
const addLog = useCallback((text, type = "info") => {
|
|
331
280
|
setLog((prev) => [...prev.slice(-50), { time: ts(), text, type }]);
|
|
332
281
|
}, []);
|
|
333
|
-
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
const activeChecks = state.checks.filter((c) => !c.details.skipped && !c.details.comingSoon);
|
|
337
|
-
const allIssues = state.checks
|
|
282
|
+
// Memoize derived lists
|
|
283
|
+
const activeChecks = useMemo(() => state.checks.filter((c) => !c.details.skipped && !c.details.comingSoon), [state.checks]);
|
|
284
|
+
const allIssues = useMemo(() => state.checks
|
|
338
285
|
.flatMap((c) => c.issues.map((i) => ({ check: c.name, ...i })))
|
|
339
286
|
.sort((a, b) => {
|
|
340
287
|
const o = { error: 0, warning: 1, info: 2 };
|
|
341
288
|
return (o[a.severity] ?? 2) - (o[b.severity] ?? 2);
|
|
342
|
-
});
|
|
289
|
+
}), [state.checks]);
|
|
343
290
|
const currentList = panel === "checks" ? activeChecks : allIssues;
|
|
291
|
+
// Clamp cursor when data changes
|
|
292
|
+
useEffect(() => {
|
|
293
|
+
setCursor((c) => Math.min(c, Math.max(0, currentList.length - 1)));
|
|
294
|
+
}, [currentList.length]);
|
|
344
295
|
const doScan = useCallback(async () => {
|
|
345
296
|
if (scanningRef.current)
|
|
346
297
|
return;
|
|
@@ -400,7 +351,20 @@ function MonitorApp({ cwd }) {
|
|
|
400
351
|
return () => { for (const w of watchers)
|
|
401
352
|
w.close(); };
|
|
402
353
|
}, [cwd, workspace, doScan, addLog, monCfg.debounceMs]);
|
|
403
|
-
|
|
354
|
+
const configOptions = useMemo(() => {
|
|
355
|
+
const cfg = pendingCfg ?? monCfg;
|
|
356
|
+
return [
|
|
357
|
+
{ key: "alertBelow", label: "Alert when score below", type: "number", value: cfg.alertBelow, path: ["alertBelow"] },
|
|
358
|
+
{ key: "alertDrop", label: "Alert on score drop ≥", type: "number", value: cfg.alertDrop, path: ["alertDrop"] },
|
|
359
|
+
{ key: "debounceMs", label: "Scan debounce (ms)", type: "number", value: cfg.debounceMs, path: ["debounceMs"] },
|
|
360
|
+
{ key: "skipTests", label: "Skip test execution", type: "toggle", value: cfg.skipTests, path: ["skipTests"] },
|
|
361
|
+
{ key: "p-score", label: "Panel: Score", type: "toggle", value: cfg.panels.score, path: ["panels", "score"] },
|
|
362
|
+
{ key: "p-checks", label: "Panel: Checks", type: "toggle", value: cfg.panels.checks, path: ["panels", "checks"] },
|
|
363
|
+
{ key: "p-activity", label: "Panel: Activity", type: "toggle", value: cfg.panels.activity, path: ["panels", "activity"] },
|
|
364
|
+
{ key: "p-issues", label: "Panel: Issues", type: "toggle", value: cfg.panels.issues, path: ["panels", "issues"] },
|
|
365
|
+
];
|
|
366
|
+
}, [pendingCfg, monCfg]);
|
|
367
|
+
// ── Keyboard — single handler for all views ──
|
|
404
368
|
useInput((input, key) => {
|
|
405
369
|
// Quit from anywhere
|
|
406
370
|
if (input === "q" || (key.ctrl && input === "c")) {
|
|
@@ -409,6 +373,9 @@ function MonitorApp({ cwd }) {
|
|
|
409
373
|
}
|
|
410
374
|
// Esc: drill up or quit
|
|
411
375
|
if (key.escape) {
|
|
376
|
+
if (mode.view === "config") {
|
|
377
|
+
setPendingCfg(null);
|
|
378
|
+
}
|
|
412
379
|
if (mode.view !== "dashboard") {
|
|
413
380
|
setMode({ view: "dashboard" });
|
|
414
381
|
setCursor(0);
|
|
@@ -417,48 +384,91 @@ function MonitorApp({ cwd }) {
|
|
|
417
384
|
exit();
|
|
418
385
|
return;
|
|
419
386
|
}
|
|
420
|
-
//
|
|
421
|
-
if (input === "r" && mode.view === "dashboard") {
|
|
387
|
+
// Rescan from dashboard or check-detail
|
|
388
|
+
if (input === "r" && (mode.view === "dashboard" || mode.view === "check-detail")) {
|
|
422
389
|
doScan();
|
|
423
390
|
return;
|
|
424
391
|
}
|
|
425
|
-
//
|
|
392
|
+
// ── Config view ──
|
|
393
|
+
if (mode.view === "config") {
|
|
394
|
+
const cfg = pendingCfg ?? { ...monCfg };
|
|
395
|
+
if (key.upArrow) {
|
|
396
|
+
setConfigCursor((c) => Math.max(0, c - 1));
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (key.downArrow) {
|
|
400
|
+
setConfigCursor((c) => Math.min(configOptions.length - 1, c + 1));
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
const opt = configOptions[configCursor];
|
|
404
|
+
if (!opt)
|
|
405
|
+
return;
|
|
406
|
+
if (opt.type === "toggle" && (input === " " || key.return)) {
|
|
407
|
+
const next = { ...cfg, panels: { ...cfg.panels } };
|
|
408
|
+
if (opt.path.length === 1)
|
|
409
|
+
next[opt.path[0]] = !opt.value;
|
|
410
|
+
else
|
|
411
|
+
next.panels[opt.path[1]] = !opt.value;
|
|
412
|
+
setPendingCfg(next);
|
|
413
|
+
}
|
|
414
|
+
if (opt.type === "number") {
|
|
415
|
+
const step = key.shift ? 10 : 1;
|
|
416
|
+
let v = opt.value;
|
|
417
|
+
if (key.rightArrow)
|
|
418
|
+
v += step;
|
|
419
|
+
if (key.leftArrow)
|
|
420
|
+
v = Math.max(0, v - step);
|
|
421
|
+
if (v !== opt.value) {
|
|
422
|
+
const next = { ...cfg, panels: { ...cfg.panels } };
|
|
423
|
+
next[opt.path[0]] = v;
|
|
424
|
+
setPendingCfg(next);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (input === "s" && pendingCfg) {
|
|
428
|
+
setMonCfg(pendingCfg);
|
|
429
|
+
saveMonitorConfig(cwd, pendingCfg);
|
|
430
|
+
addLog("Settings saved", "info");
|
|
431
|
+
setPendingCfg(null);
|
|
432
|
+
setMode({ view: "dashboard" });
|
|
433
|
+
setCursor(0);
|
|
434
|
+
}
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
// View switching (not from config — handled above)
|
|
426
438
|
if (input === "t") {
|
|
427
439
|
setMode({ view: "trends" });
|
|
428
440
|
setCursor(0);
|
|
429
441
|
return;
|
|
430
442
|
}
|
|
431
|
-
if (input === "c"
|
|
443
|
+
if (input === "c") {
|
|
432
444
|
setMode({ view: "config" });
|
|
445
|
+
setConfigCursor(0);
|
|
446
|
+
setPendingCfg({ ...monCfg, panels: { ...monCfg.panels } });
|
|
433
447
|
return;
|
|
434
448
|
}
|
|
435
|
-
// Dashboard navigation
|
|
449
|
+
// ── Dashboard navigation ──
|
|
436
450
|
if (mode.view === "dashboard") {
|
|
437
|
-
// Tab switches focused panel
|
|
438
451
|
if (key.tab) {
|
|
439
452
|
setPanel((p) => p === "checks" ? "issues" : "checks");
|
|
440
453
|
setCursor(0);
|
|
441
454
|
return;
|
|
442
455
|
}
|
|
443
|
-
// ↑↓ navigate within panel
|
|
444
456
|
if (key.upArrow)
|
|
445
457
|
setCursor((c) => Math.max(0, c - 1));
|
|
446
458
|
if (key.downArrow)
|
|
447
459
|
setCursor((c) => Math.min(currentList.length - 1, c + 1));
|
|
448
|
-
// Enter: drill into selected check
|
|
449
460
|
if (key.return && panel === "checks" && activeChecks[cursor]) {
|
|
450
461
|
setMode({ view: "check-detail", checkName: activeChecks[cursor].name });
|
|
451
462
|
setCursor(0);
|
|
452
463
|
return;
|
|
453
464
|
}
|
|
454
|
-
// Enter on issue: drill into that check
|
|
455
465
|
if (key.return && panel === "issues" && allIssues[cursor]) {
|
|
456
466
|
setMode({ view: "check-detail", checkName: allIssues[cursor].check });
|
|
457
467
|
setCursor(0);
|
|
458
468
|
return;
|
|
459
469
|
}
|
|
460
470
|
}
|
|
461
|
-
// Check detail: ↑↓ scroll, Enter copies fix prompt
|
|
471
|
+
// ── Check detail: ↑↓ scroll, Enter copies fix prompt ──
|
|
462
472
|
if (mode.view === "check-detail") {
|
|
463
473
|
const check = state.checks.find((c) => c.name === mode.checkName);
|
|
464
474
|
if (check) {
|
|
@@ -476,7 +486,7 @@ function MonitorApp({ cwd }) {
|
|
|
476
486
|
}
|
|
477
487
|
}
|
|
478
488
|
}
|
|
479
|
-
// Trends: ↑↓ scroll
|
|
489
|
+
// ── Trends: ↑↓ scroll ──
|
|
480
490
|
if (mode.view === "trends") {
|
|
481
491
|
if (key.upArrow)
|
|
482
492
|
setCursor((c) => Math.max(0, c - 1));
|
|
@@ -492,10 +502,10 @@ function MonitorApp({ cwd }) {
|
|
|
492
502
|
return (_jsxs(Box, { flexDirection: "column", height: rows, children: [_jsx(Header, { proj: proj, stack: stack, workspace: workspace, state: state }), check ? _jsx(CheckDetail, { check: check, height: rows - 3, cursor: cursor, copied: copied }) : _jsx(Text, { dimColor: true, children: " Check not found" }), _jsx(Box, { paddingX: 1, children: _jsx(Text, { dimColor: true, children: "Esc back \u00B7 \u2191\u2193 select \u00B7 Enter copy fix prompt \u00B7 q quit" }) })] }));
|
|
493
503
|
}
|
|
494
504
|
if (mode.view === "trends") {
|
|
495
|
-
return (_jsx(Box, { flexDirection: "column", height: rows, children: _jsx(TrendsScreen, { cwd: cwd, height: rows
|
|
505
|
+
return (_jsx(Box, { flexDirection: "column", height: rows, children: _jsx(TrendsScreen, { cwd: cwd, height: rows }) }));
|
|
496
506
|
}
|
|
497
507
|
if (mode.view === "config") {
|
|
498
|
-
return (_jsx(Box, { flexDirection: "column", height: rows, justifyContent: "center", alignItems: "center", children: _jsx(ConfigScreen, {
|
|
508
|
+
return (_jsx(Box, { flexDirection: "column", height: rows, justifyContent: "center", alignItems: "center", children: _jsx(ConfigScreen, { cursor: configCursor, options: configOptions }) }));
|
|
499
509
|
}
|
|
500
510
|
// ── Dashboard ──
|
|
501
511
|
const sidebarVisible = p.score || p.checks;
|