@vibecodeqa/cli 0.37.0 → 0.37.1

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 +90 -1
  2. 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,29 @@ 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
+ }
336
374
  function MonitorApp({ cwd }) {
337
375
  const { exit } = useApp();
338
376
  const { stdout } = useStdout();
@@ -465,6 +503,11 @@ function MonitorApp({ cwd }) {
465
503
  setCursor(mode.issueIdx);
466
504
  return;
467
505
  }
506
+ if (mode.view === "file-issues") {
507
+ setMode({ view: "git-changes" });
508
+ setCursor(0);
509
+ return;
510
+ }
468
511
  if (mode.view !== "dashboard") {
469
512
  setMode({ view: "dashboard" });
470
513
  setCursor(0);
@@ -529,6 +572,11 @@ function MonitorApp({ cwd }) {
529
572
  setCursor(0);
530
573
  return;
531
574
  }
575
+ if (input === "g") {
576
+ setMode({ view: "git-changes" });
577
+ setCursor(0);
578
+ return;
579
+ }
532
580
  if (input === "c") {
533
581
  setMode({ view: "config" });
534
582
  setConfigCursor(0);
@@ -591,6 +639,37 @@ function MonitorApp({ cwd }) {
591
639
  }
592
640
  }
593
641
  }
642
+ // ── Git changes: ↑↓ navigate, Enter drill into file issues ──
643
+ if (mode.view === "git-changes") {
644
+ const changes = getGitChanges(cwd);
645
+ if (key.upArrow)
646
+ setCursor((c) => Math.max(0, c - 1));
647
+ if (key.downArrow)
648
+ setCursor((c) => Math.min(changes.length - 1, c + 1));
649
+ if (key.return && changes[cursor]) {
650
+ setMode({ view: "file-issues", file: changes[cursor].file });
651
+ setCursor(0);
652
+ }
653
+ }
654
+ // ── File issues: ↑↓ navigate, Enter drill into issue detail, y copy ──
655
+ if (mode.view === "file-issues") {
656
+ const fileIssues = state.checks.flatMap((c) => c.issues.filter((i) => i.file === mode.file).map((i) => ({ check: c.name, ...i })));
657
+ if (key.upArrow)
658
+ setCursor((c) => Math.max(0, c - 1));
659
+ if (key.downArrow)
660
+ setCursor((c) => Math.min(fileIssues.length - 1, c + 1));
661
+ if (key.return && fileIssues[cursor]) {
662
+ setMode({ view: "issue-detail", checkName: fileIssues[cursor].check, issueIdx: 0 });
663
+ }
664
+ if (input === "y" && fileIssues[cursor]) {
665
+ const prompt = buildFixPrompt(fileIssues[cursor].check, fileIssues[cursor], cwd);
666
+ if (copyToClipboard(prompt)) {
667
+ setCopied(true);
668
+ addLog(`Copied fix prompt`, "info");
669
+ setTimeout(() => setCopied(false), 2000);
670
+ }
671
+ }
672
+ }
594
673
  // ── Trends: ↑↓ scroll ──
595
674
  if (mode.view === "trends") {
596
675
  if (key.upArrow)
@@ -602,6 +681,16 @@ function MonitorApp({ cwd }) {
602
681
  const proj = basename(cwd);
603
682
  const p = monCfg.panels;
604
683
  // ── Render views ──
684
+ if (mode.view === "git-changes") {
685
+ 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" }) })] }));
686
+ }
687
+ if (mode.view === "file-issues") {
688
+ const fileIssues = state.checks.flatMap((c) => c.issues.filter((i) => i.file === mode.file).map((i) => ({ check: c.name, ...i })));
689
+ 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) => {
690
+ const sel = i === cursor;
691
+ 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));
692
+ }))] }), _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" }) })] }));
693
+ }
605
694
  if (mode.view === "issue-detail") {
606
695
  const check = state.checks.find((c) => c.name === mode.checkName);
607
696
  const issue = check?.issues[mode.issueIdx];
@@ -633,7 +722,7 @@ function MonitorApp({ cwd }) {
633
722
  })] }))] })), 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
723
  const sel = panel === "issues" && i === cursor;
635
724
  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 select \u00B7 Enter drill \u00B7 Esc back \u00B7 r scan \u00B7 t trends \u00B7 c config \u00B7 q quit" }) })] }));
725
+ }), 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 g git \u00B7 t trends \u00B7 c config \u00B7 q quit" }) })] }));
637
726
  }
638
727
  function Header({ proj, stack, workspace, state }) {
639
728
  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" })] })] }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibecodeqa/cli",
3
- "version": "0.37.0",
3
+ "version": "0.37.1",
4
4
  "description": "Code health scanner for the AI coding era. 25 checks, zero config, full report.",
5
5
  "type": "module",
6
6
  "bin": {