vibe-coding-qa 0.5.0__py3-none-any.whl

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 (58) hide show
  1. dashboard/__init__.py +10 -0
  2. dashboard/backend/__init__.py +11 -0
  3. dashboard/backend/__main__.py +6 -0
  4. dashboard/backend/ai/__init__.py +6 -0
  5. dashboard/backend/ai/provider.py +102 -0
  6. dashboard/backend/ai/service.py +757 -0
  7. dashboard/backend/api/__init__.py +5 -0
  8. dashboard/backend/api/ai.py +142 -0
  9. dashboard/backend/api/dashboard.py +34 -0
  10. dashboard/backend/api/deps.py +51 -0
  11. dashboard/backend/api/findings.py +102 -0
  12. dashboard/backend/api/intel.py +41 -0
  13. dashboard/backend/api/repositories.py +62 -0
  14. dashboard/backend/api/scans.py +168 -0
  15. dashboard/backend/api/system.py +69 -0
  16. dashboard/backend/config.py +180 -0
  17. dashboard/backend/main.py +183 -0
  18. dashboard/backend/persistence/__init__.py +5 -0
  19. dashboard/backend/persistence/database.py +278 -0
  20. dashboard/backend/persistence/store.py +600 -0
  21. dashboard/backend/profiles.py +141 -0
  22. dashboard/backend/schemas.py +452 -0
  23. dashboard/backend/security.py +158 -0
  24. dashboard/backend/services/__init__.py +7 -0
  25. dashboard/backend/services/dashboard.py +172 -0
  26. dashboard/backend/services/intel.py +341 -0
  27. dashboard/backend/services/paths.py +38 -0
  28. dashboard/backend/services/repositories.py +123 -0
  29. dashboard/backend/services/scans.py +328 -0
  30. dashboard/backend/services/settings.py +49 -0
  31. dashboard/frontend/dist/assets/index-COC-CdqD.js +492 -0
  32. dashboard/frontend/dist/assets/index-hWDLRaLr.css +1 -0
  33. dashboard/frontend/dist/index.html +29 -0
  34. dashboard/frontend/dist/phone-mascots.gif +0 -0
  35. dashboard/launcher.py +48 -0
  36. vibe_coding_qa/__init__.py +140 -0
  37. vibe_coding_qa/__main__.py +5 -0
  38. vibe_coding_qa/ai_review.py +165 -0
  39. vibe_coding_qa/cli.py +157 -0
  40. vibe_coding_qa/engine.py +860 -0
  41. vibe_coding_qa/fixes.py +222 -0
  42. vibe_coding_qa/gates.py +300 -0
  43. vibe_coding_qa/profile.py +471 -0
  44. vibe_coding_qa/repo.py +188 -0
  45. vibe_coding_qa/reporters/__init__.py +253 -0
  46. vibe_coding_qa/scanners/__init__.py +195 -0
  47. vibe_coding_qa/scanners/compliance.py +167 -0
  48. vibe_coding_qa/scanners/database.py +316 -0
  49. vibe_coding_qa/scanners/dependencies.py +595 -0
  50. vibe_coding_qa/scanners/infrastructure.py +125 -0
  51. vibe_coding_qa/scanners/performance.py +84 -0
  52. vibe_coding_qa/scanners/quality.py +129 -0
  53. vibe_coding_qa/scanners/security.py +521 -0
  54. vibe_coding_qa-0.5.0.dist-info/METADATA +323 -0
  55. vibe_coding_qa-0.5.0.dist-info/RECORD +58 -0
  56. vibe_coding_qa-0.5.0.dist-info/WHEEL +5 -0
  57. vibe_coding_qa-0.5.0.dist-info/entry_points.txt +3 -0
  58. vibe_coding_qa-0.5.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,125 @@
1
+ """Infrastructure scanner: TLS, headers, container, nginx configs."""
2
+
3
+ import re
4
+ from pathlib import Path
5
+
6
+ from . import BaseScanner, load_ignore_patterns, is_ignored
7
+ from vibe_coding_qa import Finding, Severity
8
+
9
+
10
+ class InfrastructureScanner(BaseScanner):
11
+ module = "infrastructure"
12
+
13
+ def scan(self) -> list[Finding]:
14
+ ignore = load_ignore_patterns(self.repo)
15
+ self._scan_nginx(ignore)
16
+ self._scan_docker(ignore)
17
+ self._scan_csp(ignore)
18
+ return self.findings
19
+
20
+ def _scan_nginx(self, ignore: list[str]) -> None:
21
+ for path in self.repo.rglob("*.conf"):
22
+ if is_ignored(path, self.repo, ignore):
23
+ continue
24
+ if "nginx" not in path.name.lower() and "default" not in path.name.lower():
25
+ continue
26
+ text = path.read_text(encoding="utf-8", errors="ignore")
27
+ lowered = text.lower()
28
+ for i, raw in enumerate(text.splitlines(), 1):
29
+ norm = raw.strip().lower()
30
+ if "listen 80" in norm and "redirect" not in lowered:
31
+ self.add(
32
+ rule="nginx-plain-http-listen",
33
+ severity=Severity.HIGH,
34
+ file=path,
35
+ line=i,
36
+ message="Nginx listens on plain HTTP without an obvious redirect to HTTPS.",
37
+ suggestion="Redirect all HTTP traffic to HTTPS before serving content.",
38
+ confidence="medium",
39
+ )
40
+ if re.search(r"ssl_protocols\s+.*(sslv2|sslv3|tlsv1\.0|tlsv1\.1)", raw, re.IGNORECASE):
41
+ self.add(
42
+ rule="nginx-weak-tls",
43
+ severity=Severity.CRITICAL,
44
+ file=path,
45
+ line=i,
46
+ message="Nginx enables a deprecated TLS/SSL protocol.",
47
+ suggestion="Use only TLS 1.2 and TLS 1.3.",
48
+ confidence="high",
49
+ )
50
+ if "strict-transport-security" not in lowered:
51
+ self.add(
52
+ rule="nginx-missing-hsts",
53
+ severity=Severity.HIGH,
54
+ file=path,
55
+ line=1,
56
+ message="HSTS header not detected in Nginx config.",
57
+ suggestion="Add add_header Strict-Transport-Security with max-age=63072000; includeSubDomains; preload.",
58
+ confidence="medium",
59
+ )
60
+ for header in ("x-frame-options", "x-content-type-options", "referrer-policy", "permissions-policy"):
61
+ if header not in lowered:
62
+ self.add(
63
+ rule=f"nginx-missing-{header.replace('-', '_')}",
64
+ severity=Severity.MEDIUM,
65
+ file=path,
66
+ line=1,
67
+ message=f"{header.upper().replace('-', '-')} header not detected in Nginx config.",
68
+ suggestion=f"Add the {header.upper().replace('-', '-')} header to every response.",
69
+ confidence="low",
70
+ )
71
+
72
+ def _scan_docker(self, ignore: list[str]) -> None:
73
+ for path in self.repo.rglob("Dockerfile*"):
74
+ if is_ignored(path, self.repo, ignore):
75
+ continue
76
+ text = path.read_text(encoding="utf-8", errors="ignore")
77
+ for i, raw in enumerate(text.splitlines(), 1):
78
+ if re.search(r"^FROM\s+.*:latest", raw, re.IGNORECASE):
79
+ self.add(
80
+ rule="docker-latest-tag",
81
+ severity=Severity.LOW,
82
+ file=path,
83
+ line=i,
84
+ message="Docker base image uses the :latest tag.",
85
+ suggestion="Pin a specific digest or version tag for reproducible builds.",
86
+ confidence="medium",
87
+ )
88
+ if re.search(r"EXPOSE\s+\d+", raw, re.IGNORECASE) and "EXPOSE 443" not in raw.upper():
89
+ self.add(
90
+ rule="docker-exposed-port-review",
91
+ severity=Severity.INFO,
92
+ file=path,
93
+ line=i,
94
+ message="Dockerfile exposes a port; confirm TLS terminates at the edge or reverse proxy.",
95
+ suggestion="Terminate TLS at the reverse proxy or load balancer; do not expose plain HTTP directly.",
96
+ confidence="low",
97
+ )
98
+
99
+ def _scan_csp(self, ignore: list[str]) -> None:
100
+ for path in self.iter_source_files((".ts", ".tsx", ".js", ".jsx", ".py")):
101
+ if is_ignored(path, self.repo, ignore):
102
+ continue
103
+ text = path.read_text(encoding="utf-8", errors="ignore")
104
+ for i, raw in enumerate(text.splitlines(), 1):
105
+ if "content-security-policy" in raw.lower():
106
+ if "unsafe-inline" in raw.lower() or "unsafe-eval" in raw.lower():
107
+ self.add(
108
+ rule="csp-unsafe-directive",
109
+ severity=Severity.MEDIUM,
110
+ file=path,
111
+ line=i,
112
+ message="CSP uses unsafe-inline or unsafe-eval.",
113
+ suggestion="Use nonces or hashes for scripts and remove unsafe-eval where possible.",
114
+ confidence="medium",
115
+ )
116
+ return
117
+ self.add(
118
+ rule="csp-missing",
119
+ severity=Severity.MEDIUM,
120
+ file=self.repo,
121
+ line=1,
122
+ message="No Content-Security-Policy header detected in source.",
123
+ suggestion="Add a restrictive CSP through Nginx or application middleware.",
124
+ confidence="low",
125
+ )
@@ -0,0 +1,84 @@
1
+ """Performance scanner: bundle/debug artifacts, large assets, INP hints."""
2
+
3
+ import re
4
+ from pathlib import Path
5
+
6
+ from . import BaseScanner, load_ignore_patterns, is_ignored, is_likely_client_file
7
+ from vibe_coding_qa import Finding, Severity
8
+
9
+
10
+ class PerformanceScanner(BaseScanner):
11
+ module = "performance"
12
+ LARGE_FILE_BYTES = 500_000
13
+ LARGE_IMAGE_BYTES = 500_000
14
+
15
+ def scan(self) -> list[Finding]:
16
+ ignore = load_ignore_patterns(self.repo)
17
+ for path in self.iter_source_files((".ts", ".tsx", ".js", ".jsx", ".py")):
18
+ if is_ignored(path, self.repo, ignore):
19
+ continue
20
+ self._scan_source(path)
21
+ for path in self.repo.rglob("*"):
22
+ if not path.is_file():
23
+ continue
24
+ if "node_modules" in path.parts or ".git" in path.parts:
25
+ continue
26
+ if is_ignored(path, self.repo, ignore):
27
+ continue
28
+ self._scan_asset(path)
29
+ return self.findings
30
+
31
+ def _scan_source(self, path: Path) -> None:
32
+ text = path.read_text(encoding="utf-8", errors="ignore")
33
+ for i, raw in enumerate(text.splitlines(), 1):
34
+ if re.search(r"scheduler\.yield\s*\(\)|yield\s+to\s+main", raw, re.IGNORECASE):
35
+ # Positive signal; skip further INP warnings in this file.
36
+ return
37
+ if re.search(r"(onClick|onSubmit|onChange)\s*=\s*\{?\s*(async\s+)?function", raw, re.IGNORECASE):
38
+ # Heuristic: handlers that do heavy work without yielding.
39
+ pass
40
+ # Large synchronous-looking loops in handlers.
41
+ for i, raw in enumerate(text.splitlines(), 1):
42
+ if re.search(r"for\s*\([^)]*\)\s*\{[^}]*\b(fetch|await|compute|process)\b", raw, re.IGNORECASE):
43
+ if is_likely_client_file(path):
44
+ self.add(
45
+ rule="potential-blocking-handler",
46
+ severity=Severity.LOW,
47
+ file=path,
48
+ line=i,
49
+ message="Loop in an event handler may block the main thread.",
50
+ suggestion="Yield to the main thread with scheduler.yield() or break work into chunks.",
51
+ confidence="low",
52
+ )
53
+
54
+ def _scan_asset(self, path: Path) -> None:
55
+ if path.suffix.lower() in {".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"}:
56
+ try:
57
+ size = path.stat().st_size
58
+ except OSError:
59
+ return
60
+ if size > self.LARGE_IMAGE_BYTES:
61
+ self.add(
62
+ rule="large-unoptimized-image",
63
+ severity=Severity.LOW,
64
+ file=path,
65
+ line=1,
66
+ message=f"Image is {size / 1024:.0f} KB; consider compression or modern formats.",
67
+ suggestion="Compress images, serve WebP/AVIF, and use responsive sizes.",
68
+ confidence="high",
69
+ )
70
+ elif path.suffix.lower() in {".js", ".css", ".ts", ".tsx"}:
71
+ try:
72
+ size = path.stat().st_size
73
+ except OSError:
74
+ return
75
+ if size > self.LARGE_FILE_BYTES:
76
+ self.add(
77
+ rule="large-source-file",
78
+ severity=Severity.LOW,
79
+ file=path,
80
+ line=1,
81
+ message=f"Source file is {size / 1024:.0f} KB; consider splitting.",
82
+ suggestion="Split large modules to improve loading and caching.",
83
+ confidence="low",
84
+ )
@@ -0,0 +1,129 @@
1
+ """Quality scanner: TypeScript/JavaScript/Python static checks."""
2
+
3
+ import re
4
+ from pathlib import Path
5
+
6
+ from . import BaseScanner, load_ignore_patterns, is_ignored, is_likely_client_file, is_test_or_fixture
7
+ from vibe_coding_qa import Finding, Severity
8
+
9
+
10
+ class QualityScanner(BaseScanner):
11
+ module = "quality"
12
+
13
+ def scan(self) -> list[Finding]:
14
+ ignore = load_ignore_patterns(self.repo)
15
+ for path in self.iter_source_files((".ts", ".tsx", ".js", ".jsx", ".py")):
16
+ if is_ignored(path, self.repo, ignore):
17
+ continue
18
+ self._scan_console_logs(path)
19
+ self._scan_todos(path)
20
+ self._scan_any_types(path)
21
+ self._scan_missing_error_boundaries(path)
22
+ self._scan_debugger_statements(path)
23
+ self._scan_build_config(ignore)
24
+ return self.findings
25
+
26
+ def _scan_console_logs(self, path: Path) -> None:
27
+ text = path.read_text(encoding="utf-8", errors="ignore")
28
+ for i, raw in enumerate(text.splitlines(), 1):
29
+ if re.search(r"(?<!\w)console\.(log|debug|warn)\s*\(", raw):
30
+ if is_test_or_fixture(path):
31
+ continue
32
+ self.add(
33
+ rule="console-log-in-production",
34
+ severity=Severity.LOW,
35
+ file=path,
36
+ line=i,
37
+ message="Console logging statement found in source code.",
38
+ suggestion="Remove console.log/debug/warn from production builds or route through a logging service.",
39
+ confidence="high",
40
+ fixable=True,
41
+ )
42
+
43
+ def _scan_todos(self, path: Path) -> None:
44
+ text = path.read_text(encoding="utf-8", errors="ignore")
45
+ for i, raw in enumerate(text.splitlines(), 1):
46
+ if re.search(r"(?<!\w)(TODO|FIXME|HACK|XXX)\s*[:\(]", raw, re.IGNORECASE):
47
+ self.add(
48
+ rule="unfinished-todo",
49
+ severity=Severity.LOW,
50
+ file=path,
51
+ line=i,
52
+ message="Unresolved TODO/FIXME/HACK marker found.",
53
+ suggestion="Resolve or ticket the item before release.",
54
+ confidence="high",
55
+ )
56
+
57
+ def _scan_any_types(self, path: Path) -> None:
58
+ if path.suffix not in {".ts", ".tsx"}:
59
+ return
60
+ text = path.read_text(encoding="utf-8", errors="ignore")
61
+ for i, raw in enumerate(text.splitlines(), 1):
62
+ if ": any" in raw or "as any" in raw or "<any>" in raw:
63
+ self.add(
64
+ rule="unsafe-any-type",
65
+ severity=Severity.MEDIUM,
66
+ file=path,
67
+ line=i,
68
+ message="Unsafe `any` type assertion detected.",
69
+ suggestion="Replace `any` with a specific interface or use `unknown` with a type guard.",
70
+ confidence="medium",
71
+ )
72
+
73
+ def _scan_missing_error_boundaries(self, path: Path) -> None:
74
+ if not is_likely_client_file(path) or path.suffix not in {".tsx", ".jsx"}:
75
+ return
76
+ text = path.read_text(encoding="utf-8", errors="ignore")
77
+ # Only flag if a component file has no error boundary and is reasonably large.
78
+ if "ErrorBoundary" in text or "error boundary" in text.lower():
79
+ return
80
+ if len(text.splitlines()) < 40:
81
+ return
82
+ self.add(
83
+ rule="missing-error-boundary",
84
+ severity=Severity.MEDIUM,
85
+ file=path,
86
+ line=1,
87
+ message="No React ErrorBoundary detected in this component file.",
88
+ suggestion="Add an error boundary to prevent a single component crash from taking down the whole UI.",
89
+ confidence="low",
90
+ )
91
+
92
+ def _scan_debugger_statements(self, path: Path) -> None:
93
+ text = path.read_text(encoding="utf-8", errors="ignore")
94
+ for i, raw in enumerate(text.splitlines(), 1):
95
+ if re.search(r"(?<!\w)debugger;?\s*$", raw):
96
+ self.add(
97
+ rule="debugger-statement",
98
+ severity=Severity.LOW,
99
+ file=path,
100
+ line=i,
101
+ message="`debugger` statement left in source.",
102
+ suggestion="Remove debugger statements before production builds.",
103
+ confidence="high",
104
+ fixable=True,
105
+ )
106
+
107
+ def _scan_build_config(self, ignore: list[str]) -> None:
108
+ vite = self.repo / "vite.config.ts"
109
+ next_config = self.repo / "next.config.js"
110
+ for path in (vite, next_config):
111
+ if not path.is_file():
112
+ continue
113
+ if is_ignored(path, self.repo, ignore):
114
+ continue
115
+ text = path.read_text(encoding="utf-8", errors="ignore")
116
+ for i, raw in enumerate(text.splitlines(), 1):
117
+ if "sourcemap" in raw.lower() and "false" not in raw.lower():
118
+ self.add(
119
+ rule="source-maps-enabled",
120
+ severity=Severity.LOW,
121
+ file=path,
122
+ line=i,
123
+ message="Source maps may be enabled in production build config.",
124
+ suggestion="Disable source maps in production to avoid exposing source code.",
125
+ confidence="low",
126
+ )
127
+ if "console" in raw.lower() and "drop" in raw.lower():
128
+ # Config removes console logs; clear prior findings is done in engine, not here.
129
+ pass