@vibecodeqa/cli 0.37.0 → 0.37.2
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 +146 -1
- package/package.json +1 -1
package/dist/monitor.js
CHANGED
|
@@ -104,6 +104,21 @@ function buildFixPrompt(checkName, issue, cwd) {
|
|
|
104
104
|
prompt += "\n\nAnalyze the code, explain the issue, and provide the fix.";
|
|
105
105
|
return prompt;
|
|
106
106
|
}
|
|
107
|
+
function getGitChanges(cwd) {
|
|
108
|
+
try {
|
|
109
|
+
const out = execSync("git status --porcelain", { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
110
|
+
if (!out)
|
|
111
|
+
return [];
|
|
112
|
+
return out.split("\n").map((line) => {
|
|
113
|
+
const status = (line[0] === "?" ? "?" : line.trim()[0]);
|
|
114
|
+
const file = line.slice(3).trim();
|
|
115
|
+
return { status, file };
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
107
122
|
// ── Scan via child process — UI never freezes ──
|
|
108
123
|
function runScanProcess(cwd, skipTests) {
|
|
109
124
|
return new Promise((resolve) => {
|
|
@@ -333,6 +348,62 @@ function IssueDetail({ issue, checkName, cwd, height, copied }) {
|
|
|
333
348
|
const promptLines = prompt.split("\n");
|
|
334
349
|
return (_jsxs(Box, { flexDirection: "column", height: height, paddingX: 1, overflowY: "hidden", children: [_jsx(Text, { bold: true, color: "magenta", children: " \u25C8 Issue Detail" }), _jsxs(Text, { children: [_jsxs(Text, { color: sc(issue.severity), bold: true, children: [" ", issue.severity.toUpperCase(), " "] }), _jsx(Text, { dimColor: true, children: checkName }), issue.rule && _jsxs(Text, { dimColor: true, children: [" \u00B7 ", issue.rule] }), copied && _jsx(Text, { color: "green", bold: true, children: " \u2713 Copied!" })] }), _jsxs(Text, { wrap: "wrap", children: [" ", issue.message] }), issue.file && (_jsxs(Text, { color: "cyan", children: [" ", issue.file, issue.line ? `:${issue.line}` : ""] })), ctx && (_jsxs(Box, { flexDirection: "column", height: srcHeight, overflowY: "hidden", children: [_jsxs(Text, { dimColor: true, children: [" \u2500\u2500\u2500 ", ctx.filePath, " \u2500\u2500\u2500"] }), ctx.lines.slice(0, srcHeight - 2).map((l) => (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: l.highlight ? "yellow" : "gray", children: l.highlight ? "▸" : " " }), _jsxs(Text, { dimColor: true, children: [String(l.num).padStart(4), "\u2502"] }), _jsx(Text, { color: l.highlight ? "white" : undefined, children: l.text })] }, l.num))), _jsx(Text, { dimColor: true, children: " \u2500\u2500\u2500" })] })), _jsxs(Box, { flexDirection: "column", height: Math.max(3, promptHeight), overflowY: "hidden", marginTop: ctx ? 0 : 1, children: [_jsxs(Text, { bold: true, color: "green", children: [" Fix prompt ", _jsx(Text, { dimColor: true, children: "(y to copy)" })] }), promptLines.slice(0, Math.max(1, promptHeight - 1)).map((line, i) => (_jsxs(Text, { dimColor: true, wrap: "truncate", children: [" ", line] }, i)))] })] }));
|
|
335
350
|
}
|
|
351
|
+
// ── Git Changes View ──
|
|
352
|
+
function GitChangesView({ cwd, checks, height, cursor }) {
|
|
353
|
+
const changes = useMemo(() => getGitChanges(cwd), [cwd]);
|
|
354
|
+
// Cross-reference with issues
|
|
355
|
+
const issuesByFile = useMemo(() => {
|
|
356
|
+
const map = new Map();
|
|
357
|
+
for (const c of checks) {
|
|
358
|
+
for (const iss of c.issues) {
|
|
359
|
+
if (iss.file && typeof iss.file === "string") {
|
|
360
|
+
map.set(iss.file, (map.get(iss.file) || 0) + 1);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return map;
|
|
365
|
+
}, [checks]);
|
|
366
|
+
const statusColor = { M: "yellow", A: "green", D: "red", "?": "gray", R: "cyan" };
|
|
367
|
+
const visibleLines = Math.max(1, height - 5);
|
|
368
|
+
return (_jsxs(Box, { flexDirection: "column", height: height, paddingX: 1, overflowY: "hidden", children: [_jsxs(Text, { bold: true, color: "magenta", children: [" \u25C8 Git Changes (", changes.length, ")"] }), changes.length === 0 ? (_jsx(Text, { dimColor: true, children: " Working tree clean \u2014 no uncommitted changes." })) : (_jsxs(_Fragment, { children: [changes.slice(0, visibleLines).map((ch, i) => {
|
|
369
|
+
const sel = i === cursor;
|
|
370
|
+
const count = issuesByFile.get(ch.file) || 0;
|
|
371
|
+
return (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: sel ? "white" : "gray", children: sel ? "▸" : " " }), _jsxs(Text, { color: statusColor[ch.status] || "gray", bold: true, children: [" ", ch.status, " "] }), _jsxs(Text, { color: sel ? "white" : undefined, children: [ch.file, " "] }), count > 0 ? (_jsxs(Text, { color: "yellow", children: [count, " issue", count !== 1 ? "s" : ""] })) : (_jsx(Text, { color: "green", children: "clean" }))] }, ch.file));
|
|
372
|
+
}), changes.length > visibleLines && _jsxs(Text, { dimColor: true, children: [" +", changes.length - visibleLines, " more"] })] }))] }));
|
|
373
|
+
}
|
|
374
|
+
// ── All Files View ──
|
|
375
|
+
function AllFilesView({ checks, height, cursor }) {
|
|
376
|
+
// Build file list from all issues, sorted by issue count descending
|
|
377
|
+
const fileMap = useMemo(() => {
|
|
378
|
+
const map = new Map();
|
|
379
|
+
for (const c of checks) {
|
|
380
|
+
for (const iss of c.issues) {
|
|
381
|
+
if (!iss.file || typeof iss.file !== "string")
|
|
382
|
+
continue;
|
|
383
|
+
const entry = map.get(iss.file) || { errors: 0, warnings: 0, infos: 0 };
|
|
384
|
+
if (iss.severity === "error")
|
|
385
|
+
entry.errors++;
|
|
386
|
+
else if (iss.severity === "warning")
|
|
387
|
+
entry.warnings++;
|
|
388
|
+
else
|
|
389
|
+
entry.infos++;
|
|
390
|
+
map.set(iss.file, entry);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return [...map.entries()]
|
|
394
|
+
.map(([file, counts]) => ({ file, total: counts.errors + counts.warnings + counts.infos, ...counts }))
|
|
395
|
+
.sort((a, b) => b.total - a.total);
|
|
396
|
+
}, [checks]);
|
|
397
|
+
const visibleLines = Math.max(1, height - 5);
|
|
398
|
+
// Scroll window
|
|
399
|
+
const scrollStart = Math.max(0, Math.min(cursor - Math.floor(visibleLines / 2), fileMap.length - visibleLines));
|
|
400
|
+
const visible = fileMap.slice(scrollStart, scrollStart + visibleLines);
|
|
401
|
+
return (_jsxs(Box, { flexDirection: "column", height: height, paddingX: 1, overflowY: "hidden", children: [_jsxs(Text, { bold: true, color: "magenta", children: [" \u25C8 Files with Issues (", fileMap.length, ")"] }), _jsx(Text, { dimColor: true, children: " sorted by issue count \u00B7 Enter to drill in" }), _jsx(Text, { children: " " }), fileMap.length === 0 ? (_jsx(Text, { color: "green", children: " No issues in any file." })) : (_jsxs(_Fragment, { children: [visible.map((f, i) => {
|
|
402
|
+
const idx = scrollStart + i;
|
|
403
|
+
const sel = idx === cursor;
|
|
404
|
+
return (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: sel ? "white" : "gray", children: sel ? "▸" : " " }), f.errors > 0 ? _jsxs(Text, { color: "red", bold: true, children: [String(f.errors).padStart(2), "E "] }) : _jsx(Text, { dimColor: true, children: " " }), f.warnings > 0 ? _jsxs(Text, { color: "yellow", children: [String(f.warnings).padStart(2), "W "] }) : _jsx(Text, { dimColor: true, children: " " }), f.infos > 0 ? _jsxs(Text, { dimColor: true, children: [String(f.infos).padStart(2), "I "] }) : _jsx(Text, { dimColor: true, children: " " }), _jsx(Text, { color: sel ? "white" : "cyan", children: f.file })] }, f.file));
|
|
405
|
+
}), fileMap.length > scrollStart + visibleLines && _jsxs(Text, { dimColor: true, children: [" +", fileMap.length - scrollStart - visibleLines, " more (\u2193)"] })] }))] }));
|
|
406
|
+
}
|
|
336
407
|
function MonitorApp({ cwd }) {
|
|
337
408
|
const { exit } = useApp();
|
|
338
409
|
const { stdout } = useStdout();
|
|
@@ -465,6 +536,11 @@ function MonitorApp({ cwd }) {
|
|
|
465
536
|
setCursor(mode.issueIdx);
|
|
466
537
|
return;
|
|
467
538
|
}
|
|
539
|
+
if (mode.view === "file-issues") {
|
|
540
|
+
setMode({ view: "dashboard" });
|
|
541
|
+
setCursor(0);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
468
544
|
if (mode.view !== "dashboard") {
|
|
469
545
|
setMode({ view: "dashboard" });
|
|
470
546
|
setCursor(0);
|
|
@@ -529,6 +605,16 @@ function MonitorApp({ cwd }) {
|
|
|
529
605
|
setCursor(0);
|
|
530
606
|
return;
|
|
531
607
|
}
|
|
608
|
+
if (input === "g") {
|
|
609
|
+
setMode({ view: "git-changes" });
|
|
610
|
+
setCursor(0);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
if (input === "f") {
|
|
614
|
+
setMode({ view: "all-files" });
|
|
615
|
+
setCursor(0);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
532
618
|
if (input === "c") {
|
|
533
619
|
setMode({ view: "config" });
|
|
534
620
|
setConfigCursor(0);
|
|
@@ -591,6 +677,52 @@ function MonitorApp({ cwd }) {
|
|
|
591
677
|
}
|
|
592
678
|
}
|
|
593
679
|
}
|
|
680
|
+
// ── All files: ↑↓ navigate, Enter drill into file issues ──
|
|
681
|
+
if (mode.view === "all-files") {
|
|
682
|
+
const fileMap = state.checks
|
|
683
|
+
.flatMap((c) => c.issues.filter((i) => i.file && typeof i.file === "string").map((i) => i.file))
|
|
684
|
+
.reduce((map, f) => { map.set(f, (map.get(f) || 0) + 1); return map; }, new Map());
|
|
685
|
+
const files = [...fileMap.keys()].sort((a, b) => (fileMap.get(b) || 0) - (fileMap.get(a) || 0));
|
|
686
|
+
if (key.upArrow)
|
|
687
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
688
|
+
if (key.downArrow)
|
|
689
|
+
setCursor((c) => Math.min(files.length - 1, c + 1));
|
|
690
|
+
if (key.return && files[cursor]) {
|
|
691
|
+
setMode({ view: "file-issues", file: files[cursor] });
|
|
692
|
+
setCursor(0);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
// ── Git changes: ↑↓ navigate, Enter drill into file issues ──
|
|
696
|
+
if (mode.view === "git-changes") {
|
|
697
|
+
const changes = getGitChanges(cwd);
|
|
698
|
+
if (key.upArrow)
|
|
699
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
700
|
+
if (key.downArrow)
|
|
701
|
+
setCursor((c) => Math.min(changes.length - 1, c + 1));
|
|
702
|
+
if (key.return && changes[cursor]) {
|
|
703
|
+
setMode({ view: "file-issues", file: changes[cursor].file });
|
|
704
|
+
setCursor(0);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
// ── File issues: ↑↓ navigate, Enter drill into issue detail, y copy ──
|
|
708
|
+
if (mode.view === "file-issues") {
|
|
709
|
+
const fileIssues = state.checks.flatMap((c) => c.issues.filter((i) => i.file === mode.file).map((i) => ({ check: c.name, ...i })));
|
|
710
|
+
if (key.upArrow)
|
|
711
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
712
|
+
if (key.downArrow)
|
|
713
|
+
setCursor((c) => Math.min(fileIssues.length - 1, c + 1));
|
|
714
|
+
if (key.return && fileIssues[cursor]) {
|
|
715
|
+
setMode({ view: "issue-detail", checkName: fileIssues[cursor].check, issueIdx: 0 });
|
|
716
|
+
}
|
|
717
|
+
if (input === "y" && fileIssues[cursor]) {
|
|
718
|
+
const prompt = buildFixPrompt(fileIssues[cursor].check, fileIssues[cursor], cwd);
|
|
719
|
+
if (copyToClipboard(prompt)) {
|
|
720
|
+
setCopied(true);
|
|
721
|
+
addLog(`Copied fix prompt`, "info");
|
|
722
|
+
setTimeout(() => setCopied(false), 2000);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
594
726
|
// ── Trends: ↑↓ scroll ──
|
|
595
727
|
if (mode.view === "trends") {
|
|
596
728
|
if (key.upArrow)
|
|
@@ -602,6 +734,19 @@ function MonitorApp({ cwd }) {
|
|
|
602
734
|
const proj = basename(cwd);
|
|
603
735
|
const p = monCfg.panels;
|
|
604
736
|
// ── Render views ──
|
|
737
|
+
if (mode.view === "all-files") {
|
|
738
|
+
return (_jsxs(Box, { flexDirection: "column", height: rows, children: [_jsx(Header, { proj: proj, stack: stack, workspace: workspace, state: state }), _jsx(AllFilesView, { checks: state.checks, height: rows - 3, cursor: cursor }), _jsx(Box, { paddingX: 1, children: _jsx(Text, { dimColor: true, children: "Esc back \u00B7 \u2191\u2193 select \u00B7 Enter view file issues \u00B7 q quit" }) })] }));
|
|
739
|
+
}
|
|
740
|
+
if (mode.view === "git-changes") {
|
|
741
|
+
return (_jsxs(Box, { flexDirection: "column", height: rows, children: [_jsx(Header, { proj: proj, stack: stack, workspace: workspace, state: state }), _jsx(GitChangesView, { cwd: cwd, checks: state.checks, height: rows - 3, cursor: cursor }), _jsx(Box, { paddingX: 1, children: _jsx(Text, { dimColor: true, children: "Esc back \u00B7 \u2191\u2193 select \u00B7 Enter view file issues \u00B7 q quit" }) })] }));
|
|
742
|
+
}
|
|
743
|
+
if (mode.view === "file-issues") {
|
|
744
|
+
const fileIssues = state.checks.flatMap((c) => c.issues.filter((i) => i.file === mode.file).map((i) => ({ check: c.name, ...i })));
|
|
745
|
+
return (_jsxs(Box, { flexDirection: "column", height: rows, children: [_jsx(Header, { proj: proj, stack: stack, workspace: workspace, state: state }), _jsxs(Box, { flexDirection: "column", height: rows - 3, paddingX: 1, overflowY: "hidden", children: [_jsxs(Text, { bold: true, color: "magenta", children: [" \u25C8 ", mode.file] }), _jsxs(Text, { dimColor: true, children: [" ", fileIssues.length, " issue", fileIssues.length !== 1 ? "s" : "", copied && _jsx(Text, { color: "green", bold: true, children: " \u2713 Copied!" })] }), _jsx(Text, { children: " " }), fileIssues.length === 0 ? (_jsx(Text, { color: "green", children: " No issues in this file." })) : (fileIssues.slice(0, rows - 8).map((iss, i) => {
|
|
746
|
+
const sel = i === cursor;
|
|
747
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { color: sel ? "white" : "gray", children: sel ? "▸" : " " }), _jsxs(Text, { color: sc(iss.severity), bold: true, children: [iss.severity[0].toUpperCase(), " "] }), iss.line && _jsx(Text, { color: "cyan", children: String(iss.line).padEnd(5) }), _jsx(Text, { dimColor: true, children: iss.check.padEnd(14) }), iss.rule && _jsxs(Text, { dimColor: true, children: ["(", iss.rule, ") "] })] }), _jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: sel ? "white" : "gray", children: " " }), _jsx(Text, { color: sel ? "white" : undefined, children: iss.message })] })] }, i));
|
|
748
|
+
}))] }), _jsx(Box, { paddingX: 1, children: _jsx(Text, { dimColor: true, children: "Esc back \u00B7 \u2191\u2193 select \u00B7 Enter source \u00B7 y copy prompt \u00B7 q quit" }) })] }));
|
|
749
|
+
}
|
|
605
750
|
if (mode.view === "issue-detail") {
|
|
606
751
|
const check = state.checks.find((c) => c.name === mode.checkName);
|
|
607
752
|
const issue = check?.issues[mode.issueIdx];
|
|
@@ -633,7 +778,7 @@ function MonitorApp({ cwd }) {
|
|
|
633
778
|
})] }))] })), mainVisible && (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [p.activity && _jsx(ActivityPanel, { log: log, height: activityH }), p.issues && (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: panel === "issues" ? "magenta" : "gray", paddingX: 1, height: issuesH, overflowY: "hidden", children: [_jsxs(Text, { bold: true, color: "magenta", children: [" ", "\u25C8 Issues (", allIssues.length, ") ", panel === "issues" && _jsx(Text, { dimColor: true, children: "\u25C4" })] }), allIssues.slice(0, issuesH - 3).map((iss, i) => {
|
|
634
779
|
const sel = panel === "issues" && i === cursor;
|
|
635
780
|
return (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: sel ? "white" : "gray", children: sel ? "▸" : " " }), _jsxs(Text, { color: sc(iss.severity), bold: true, children: [iss.severity[0].toUpperCase(), " "] }), _jsxs(Text, { dimColor: true, children: [(iss.check || "").slice(0, 11).padEnd(11), " "] }), iss.file && _jsxs(Text, { color: "cyan", children: [basename(String(iss.file)).slice(0, 18).padEnd(18), " "] }), _jsx(Text, { children: iss.message.slice(0, 40) })] }, i));
|
|
636
|
-
}), allIssues.length > issuesH - 3 && _jsxs(Text, { dimColor: true, children: [" +", allIssues.length - (issuesH - 3), " more"] })] }))] })), !sidebarVisible && !mainVisible && (_jsx(Box, { height: bodyRows, justifyContent: "center", alignItems: "center", children: _jsx(Text, { dimColor: true, children: "All panels hidden. Press c to configure." }) }))] }), _jsx(Box, { paddingX: 1, justifyContent: "space-between", children: _jsx(Text, { dimColor: true, children: "Tab panel \u00B7 \u2191\u2193
|
|
781
|
+
}), allIssues.length > issuesH - 3 && _jsxs(Text, { dimColor: true, children: [" +", allIssues.length - (issuesH - 3), " more"] })] }))] })), !sidebarVisible && !mainVisible && (_jsx(Box, { height: bodyRows, justifyContent: "center", alignItems: "center", children: _jsx(Text, { dimColor: true, children: "All panels hidden. Press c to configure." }) }))] }), _jsx(Box, { paddingX: 1, justifyContent: "space-between", children: _jsx(Text, { dimColor: true, children: "Tab panel \u00B7 \u2191\u2193 Enter Esc \u00B7 r scan \u00B7 f files \u00B7 g git \u00B7 t trends \u00B7 c config \u00B7 q" }) })] }));
|
|
637
782
|
}
|
|
638
783
|
function Header({ proj, stack, workspace, state }) {
|
|
639
784
|
return (_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" })] })] }));
|