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.
- dashboard/__init__.py +10 -0
- dashboard/backend/__init__.py +11 -0
- dashboard/backend/__main__.py +6 -0
- dashboard/backend/ai/__init__.py +6 -0
- dashboard/backend/ai/provider.py +102 -0
- dashboard/backend/ai/service.py +757 -0
- dashboard/backend/api/__init__.py +5 -0
- dashboard/backend/api/ai.py +142 -0
- dashboard/backend/api/dashboard.py +34 -0
- dashboard/backend/api/deps.py +51 -0
- dashboard/backend/api/findings.py +102 -0
- dashboard/backend/api/intel.py +41 -0
- dashboard/backend/api/repositories.py +62 -0
- dashboard/backend/api/scans.py +168 -0
- dashboard/backend/api/system.py +69 -0
- dashboard/backend/config.py +180 -0
- dashboard/backend/main.py +183 -0
- dashboard/backend/persistence/__init__.py +5 -0
- dashboard/backend/persistence/database.py +278 -0
- dashboard/backend/persistence/store.py +600 -0
- dashboard/backend/profiles.py +141 -0
- dashboard/backend/schemas.py +452 -0
- dashboard/backend/security.py +158 -0
- dashboard/backend/services/__init__.py +7 -0
- dashboard/backend/services/dashboard.py +172 -0
- dashboard/backend/services/intel.py +341 -0
- dashboard/backend/services/paths.py +38 -0
- dashboard/backend/services/repositories.py +123 -0
- dashboard/backend/services/scans.py +328 -0
- dashboard/backend/services/settings.py +49 -0
- dashboard/frontend/dist/assets/index-COC-CdqD.js +492 -0
- dashboard/frontend/dist/assets/index-hWDLRaLr.css +1 -0
- dashboard/frontend/dist/index.html +29 -0
- dashboard/frontend/dist/phone-mascots.gif +0 -0
- dashboard/launcher.py +48 -0
- vibe_coding_qa/__init__.py +140 -0
- vibe_coding_qa/__main__.py +5 -0
- vibe_coding_qa/ai_review.py +165 -0
- vibe_coding_qa/cli.py +157 -0
- vibe_coding_qa/engine.py +860 -0
- vibe_coding_qa/fixes.py +222 -0
- vibe_coding_qa/gates.py +300 -0
- vibe_coding_qa/profile.py +471 -0
- vibe_coding_qa/repo.py +188 -0
- vibe_coding_qa/reporters/__init__.py +253 -0
- vibe_coding_qa/scanners/__init__.py +195 -0
- vibe_coding_qa/scanners/compliance.py +167 -0
- vibe_coding_qa/scanners/database.py +316 -0
- vibe_coding_qa/scanners/dependencies.py +595 -0
- vibe_coding_qa/scanners/infrastructure.py +125 -0
- vibe_coding_qa/scanners/performance.py +84 -0
- vibe_coding_qa/scanners/quality.py +129 -0
- vibe_coding_qa/scanners/security.py +521 -0
- vibe_coding_qa-0.5.0.dist-info/METADATA +323 -0
- vibe_coding_qa-0.5.0.dist-info/RECORD +58 -0
- vibe_coding_qa-0.5.0.dist-info/WHEEL +5 -0
- vibe_coding_qa-0.5.0.dist-info/entry_points.txt +3 -0
- vibe_coding_qa-0.5.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Scan profiles.
|
|
2
|
+
|
|
3
|
+
A profile is not a label: it selects which scanner modules actually run, whether
|
|
4
|
+
the isolated test suite and OSV lookups are attempted, whether optional AI review
|
|
5
|
+
is requested, and the minimum score the release gate enforces. All of these map
|
|
6
|
+
onto real engine parameters.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
ALL_MODULES: tuple[str, ...] = (
|
|
12
|
+
"security",
|
|
13
|
+
"quality",
|
|
14
|
+
"database",
|
|
15
|
+
"infrastructure",
|
|
16
|
+
"performance",
|
|
17
|
+
"compliance",
|
|
18
|
+
"dependencies",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class ScanProfile:
|
|
24
|
+
key: str
|
|
25
|
+
name: str
|
|
26
|
+
description: str
|
|
27
|
+
modules: tuple[str, ...]
|
|
28
|
+
run_tests: bool
|
|
29
|
+
osv: bool
|
|
30
|
+
ai_review: bool
|
|
31
|
+
minimum_score: int
|
|
32
|
+
notes: tuple[str, ...] = ()
|
|
33
|
+
|
|
34
|
+
def to_dict(self) -> dict:
|
|
35
|
+
return {
|
|
36
|
+
"key": self.key,
|
|
37
|
+
"name": self.name,
|
|
38
|
+
"description": self.description,
|
|
39
|
+
"modules": list(self.modules),
|
|
40
|
+
"run_tests": self.run_tests,
|
|
41
|
+
"osv": self.osv,
|
|
42
|
+
"ai_review": self.ai_review,
|
|
43
|
+
"minimum_score": self.minimum_score,
|
|
44
|
+
"notes": list(self.notes),
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
PROFILES: dict[str, ScanProfile] = {
|
|
49
|
+
"quick": ScanProfile(
|
|
50
|
+
key="quick",
|
|
51
|
+
name="Quick Scan",
|
|
52
|
+
description="Fast static pass over security and dependency risks. No tests, no network lookups.",
|
|
53
|
+
modules=("security", "dependencies"),
|
|
54
|
+
run_tests=False,
|
|
55
|
+
osv=False,
|
|
56
|
+
ai_review=False,
|
|
57
|
+
minimum_score=50,
|
|
58
|
+
notes=("Static analysis only.", "No test execution.", "No OSV advisory lookups."),
|
|
59
|
+
),
|
|
60
|
+
"standard": ScanProfile(
|
|
61
|
+
key="standard",
|
|
62
|
+
name="Standard Scan",
|
|
63
|
+
description="All static scanners plus optional OSV advisory lookups and isolated tests when permitted.",
|
|
64
|
+
modules=ALL_MODULES,
|
|
65
|
+
run_tests=True,
|
|
66
|
+
osv=True,
|
|
67
|
+
ai_review=False,
|
|
68
|
+
minimum_score=60,
|
|
69
|
+
notes=("Runs every static scanner.", "Tests run only with explicit permission."),
|
|
70
|
+
),
|
|
71
|
+
"deep": ScanProfile(
|
|
72
|
+
key="deep",
|
|
73
|
+
name="Deep Scan",
|
|
74
|
+
description="Everything in Standard plus optional AI review of critical and high findings.",
|
|
75
|
+
modules=ALL_MODULES,
|
|
76
|
+
run_tests=True,
|
|
77
|
+
osv=True,
|
|
78
|
+
ai_review=True,
|
|
79
|
+
minimum_score=60,
|
|
80
|
+
notes=("Requires a configured AI provider for the AI stage.",),
|
|
81
|
+
),
|
|
82
|
+
"production_readiness": ScanProfile(
|
|
83
|
+
key="production_readiness",
|
|
84
|
+
name="Production Readiness",
|
|
85
|
+
description="Full audit with a stricter release threshold; unverified tests block a PASS.",
|
|
86
|
+
modules=ALL_MODULES,
|
|
87
|
+
run_tests=True,
|
|
88
|
+
osv=True,
|
|
89
|
+
ai_review=False,
|
|
90
|
+
minimum_score=75,
|
|
91
|
+
notes=("Minimum score 75.", "Requires a verified test run for a PASS verdict."),
|
|
92
|
+
),
|
|
93
|
+
"security_audit": ScanProfile(
|
|
94
|
+
key="security_audit",
|
|
95
|
+
name="Security Audit",
|
|
96
|
+
description="Focused on exploitable risk: security, database exposure, infrastructure and compliance.",
|
|
97
|
+
modules=("security", "database", "infrastructure", "compliance", "dependencies"),
|
|
98
|
+
run_tests=False,
|
|
99
|
+
osv=True,
|
|
100
|
+
ai_review=True,
|
|
101
|
+
minimum_score=65,
|
|
102
|
+
notes=("Skips quality and performance scanners.",),
|
|
103
|
+
),
|
|
104
|
+
"supabase_audit": ScanProfile(
|
|
105
|
+
key="supabase_audit",
|
|
106
|
+
name="Supabase Audit",
|
|
107
|
+
description="Row-level-security and data-exposure focused audit.",
|
|
108
|
+
modules=("database", "security", "compliance", "infrastructure"),
|
|
109
|
+
run_tests=False,
|
|
110
|
+
osv=False,
|
|
111
|
+
ai_review=False,
|
|
112
|
+
minimum_score=60,
|
|
113
|
+
notes=("Emphasises Postgres RLS and client-side data exposure.",),
|
|
114
|
+
),
|
|
115
|
+
"nextjs_audit": ScanProfile(
|
|
116
|
+
key="nextjs_audit",
|
|
117
|
+
name="Next.js Audit",
|
|
118
|
+
description="Frontend/API security, quality and performance for Next.js and React applications.",
|
|
119
|
+
modules=("security", "quality", "dependencies", "performance", "infrastructure", "compliance"),
|
|
120
|
+
run_tests=True,
|
|
121
|
+
osv=True,
|
|
122
|
+
ai_review=False,
|
|
123
|
+
minimum_score=60,
|
|
124
|
+
notes=("Runs the npm/vitest/jest suite only with explicit permission.",),
|
|
125
|
+
),
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
DEFAULT_PROFILE = "standard"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def get_profile(key: str | None) -> ScanProfile:
|
|
132
|
+
if not key:
|
|
133
|
+
return PROFILES[DEFAULT_PROFILE]
|
|
134
|
+
profile = PROFILES.get(key)
|
|
135
|
+
if profile is None:
|
|
136
|
+
raise KeyError(key)
|
|
137
|
+
return profile
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def list_profiles() -> list[dict]:
|
|
141
|
+
return [profile.to_dict() for profile in PROFILES.values()]
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
"""Request/response schemas for the dashboard API (Pydantic v2)."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any, Literal
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
SourceType = Literal["local", "github"]
|
|
10
|
+
ScanStatus = Literal["queued", "running", "completed", "failed", "cancelled"]
|
|
11
|
+
FindingStatus = Literal["open", "reviewed", "resolved", "dismissed", "unverified"]
|
|
12
|
+
Severity = Literal["critical", "high", "medium", "low", "info"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ErrorResponse(BaseModel):
|
|
16
|
+
detail: str
|
|
17
|
+
code: str = "error"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ------------------------------------------------------------------ repositories
|
|
21
|
+
class RepositoryCreate(BaseModel):
|
|
22
|
+
name: str | None = Field(default=None, max_length=200)
|
|
23
|
+
source: str = Field(min_length=1, max_length=500)
|
|
24
|
+
source_type: SourceType = "local"
|
|
25
|
+
|
|
26
|
+
@field_validator("source")
|
|
27
|
+
@classmethod
|
|
28
|
+
def _strip(cls, value: str) -> str:
|
|
29
|
+
return value.strip()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class LatestScanSummary(BaseModel):
|
|
33
|
+
id: int
|
|
34
|
+
status: ScanStatus
|
|
35
|
+
overall_score: float | None = None
|
|
36
|
+
gate: str | None = None
|
|
37
|
+
critical_count: int | None = None
|
|
38
|
+
high_count: int | None = None
|
|
39
|
+
completed_at: str | None = None
|
|
40
|
+
created_at: str | None = None
|
|
41
|
+
test_status: str | None = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class RepositoryOut(BaseModel):
|
|
45
|
+
model_config = ConfigDict(from_attributes=True)
|
|
46
|
+
|
|
47
|
+
id: int
|
|
48
|
+
name: str
|
|
49
|
+
source: str
|
|
50
|
+
source_type: str
|
|
51
|
+
created_at: str
|
|
52
|
+
updated_at: str
|
|
53
|
+
main_language: str | None = None
|
|
54
|
+
frameworks: list[str] = Field(default_factory=list)
|
|
55
|
+
package_manager: str | None = None
|
|
56
|
+
database_tech: list[str] = Field(default_factory=list)
|
|
57
|
+
auth_tech: list[str] = Field(default_factory=list)
|
|
58
|
+
deployment: list[str] = Field(default_factory=list)
|
|
59
|
+
default_branch: str | None = None
|
|
60
|
+
last_commit_sha: str | None = None
|
|
61
|
+
last_scan_id: int | None = None
|
|
62
|
+
latest_scan: LatestScanSummary | None = None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# ------------------------------------------------------------------------- scans
|
|
66
|
+
class ScanOptions(BaseModel):
|
|
67
|
+
"""Per-scan overrides.
|
|
68
|
+
|
|
69
|
+
Resource fields default to ``None`` so "not specified" is distinguishable
|
|
70
|
+
from "explicitly set": omitted values fall back to the operator's persisted
|
|
71
|
+
scan settings (see ``services.settings``), which is what makes the scan
|
|
72
|
+
settings screen functional rather than decorative.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
static_only: bool = False
|
|
76
|
+
explicit_permission: bool = False
|
|
77
|
+
test_timeout: int | None = Field(default=None, ge=1, le=7200)
|
|
78
|
+
test_image: str | None = None
|
|
79
|
+
test_command: str | None = None
|
|
80
|
+
max_memory_mb: int | None = Field(default=None, ge=64, le=16384)
|
|
81
|
+
max_cpu_seconds: int | None = Field(default=None, ge=1, le=3600)
|
|
82
|
+
max_snapshot_mb: int | None = Field(default=None, ge=1, le=2048)
|
|
83
|
+
max_write_mb: int | None = Field(default=None, ge=2, le=2048)
|
|
84
|
+
osv: bool | None = None
|
|
85
|
+
ai_review: bool | None = None
|
|
86
|
+
share_source_context: bool = False
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class ScanCreate(BaseModel):
|
|
90
|
+
repository_id: int
|
|
91
|
+
profile: str = "standard"
|
|
92
|
+
options: ScanOptions = Field(default_factory=ScanOptions)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class ScanOut(BaseModel):
|
|
96
|
+
model_config = ConfigDict(from_attributes=True)
|
|
97
|
+
|
|
98
|
+
id: int
|
|
99
|
+
repository_id: int
|
|
100
|
+
repository_name: str | None = None
|
|
101
|
+
repository_source: str | None = None
|
|
102
|
+
status: ScanStatus
|
|
103
|
+
profile: str
|
|
104
|
+
created_at: str
|
|
105
|
+
started_at: str | None = None
|
|
106
|
+
completed_at: str | None = None
|
|
107
|
+
commit_sha: str | None = None
|
|
108
|
+
overall_score: float | None = None
|
|
109
|
+
gate: str | None = None
|
|
110
|
+
critical_count: int | None = 0
|
|
111
|
+
high_count: int | None = 0
|
|
112
|
+
total_findings: int | None = 0
|
|
113
|
+
test_status: str | None = None
|
|
114
|
+
tests_executed: int | None = None
|
|
115
|
+
tests_passed: int | None = None
|
|
116
|
+
tests_failed: int | None = None
|
|
117
|
+
tests_skipped: int | None = None
|
|
118
|
+
duration_ms: int | None = None
|
|
119
|
+
scanner_versions: dict[str, Any] = Field(default_factory=dict)
|
|
120
|
+
coverage_gaps: list[str] = Field(default_factory=list)
|
|
121
|
+
ai_review_status: str | None = None
|
|
122
|
+
error: str | None = None
|
|
123
|
+
options: dict[str, Any] = Field(default_factory=dict)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class ScanListOut(BaseModel):
|
|
127
|
+
items: list[ScanOut]
|
|
128
|
+
total: int
|
|
129
|
+
limit: int
|
|
130
|
+
offset: int
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class ScanEventOut(BaseModel):
|
|
134
|
+
id: int
|
|
135
|
+
scan_id: int
|
|
136
|
+
ts: str
|
|
137
|
+
stage: str
|
|
138
|
+
status: str | None = None
|
|
139
|
+
message: str | None = None
|
|
140
|
+
data: dict[str, Any] = Field(default_factory=dict)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# ---------------------------------------------------------------------- findings
|
|
144
|
+
class FindingOut(BaseModel):
|
|
145
|
+
model_config = ConfigDict(from_attributes=True)
|
|
146
|
+
|
|
147
|
+
id: int
|
|
148
|
+
scan_id: int
|
|
149
|
+
repository_id: int
|
|
150
|
+
repository_name: str | None = None
|
|
151
|
+
fingerprint: str
|
|
152
|
+
rule: str
|
|
153
|
+
module: str
|
|
154
|
+
severity: Severity
|
|
155
|
+
file: str
|
|
156
|
+
line: int
|
|
157
|
+
column: int = 0
|
|
158
|
+
message: str | None = ""
|
|
159
|
+
suggestion: str | None = ""
|
|
160
|
+
confidence: str | None = "medium"
|
|
161
|
+
fixable: int = 0
|
|
162
|
+
status: FindingStatus
|
|
163
|
+
first_seen_scan_id: int | None = None
|
|
164
|
+
created_at: str
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class FindingDetailOut(FindingOut):
|
|
168
|
+
scan_profile: str | None = None
|
|
169
|
+
scan_completed_at: str | None = None
|
|
170
|
+
evidence: list[str] = Field(default_factory=list)
|
|
171
|
+
history: list[dict[str, Any]] = Field(default_factory=list)
|
|
172
|
+
related: list[dict[str, Any]] = Field(default_factory=list)
|
|
173
|
+
latest_ai_review: dict[str, Any] | None = None
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class FindingListOut(BaseModel):
|
|
177
|
+
items: list[FindingOut]
|
|
178
|
+
total: int
|
|
179
|
+
limit: int
|
|
180
|
+
offset: int
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class FindingStatusUpdate(BaseModel):
|
|
184
|
+
status: Literal["open", "reviewed", "dismissed", "unverified"]
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# ---------------------------------------------------------------------- dashboard
|
|
188
|
+
class SummaryCards(BaseModel):
|
|
189
|
+
overall_score: float | None
|
|
190
|
+
critical: int
|
|
191
|
+
high: int
|
|
192
|
+
total_open: int
|
|
193
|
+
tests_executed: int | None
|
|
194
|
+
tests_passed: int | None
|
|
195
|
+
gate: str | None
|
|
196
|
+
repository_count: int
|
|
197
|
+
latest_scan_duration_ms: int | None
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class OverviewOut(BaseModel):
|
|
201
|
+
summary: SummaryCards
|
|
202
|
+
severity_breakdown: dict[str, int]
|
|
203
|
+
category_breakdown: dict[str, int]
|
|
204
|
+
score_history: list[dict[str, Any]]
|
|
205
|
+
findings_trend: list[dict[str, Any]]
|
|
206
|
+
test_history: list[dict[str, Any]]
|
|
207
|
+
repository_health: list[dict[str, Any]]
|
|
208
|
+
has_data: bool
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class ActivityItem(BaseModel):
|
|
212
|
+
kind: str
|
|
213
|
+
title: str
|
|
214
|
+
detail: str
|
|
215
|
+
at: str | None
|
|
216
|
+
repository_id: int | None = None
|
|
217
|
+
scan_id: int | None = None
|
|
218
|
+
finding_id: int | None = None
|
|
219
|
+
severity: str | None = None
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class ActivityOut(BaseModel):
|
|
223
|
+
items: list[ActivityItem]
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# ---------------------------------------------------------------------------- AI
|
|
227
|
+
class AIChatRequest(BaseModel):
|
|
228
|
+
message: str = Field(min_length=1, max_length=8000)
|
|
229
|
+
repository_id: int | None = None
|
|
230
|
+
conversation_id: int | None = None
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class AIMessageOut(BaseModel):
|
|
234
|
+
id: int
|
|
235
|
+
role: str
|
|
236
|
+
content: str
|
|
237
|
+
created_at: str
|
|
238
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class AIChatResponse(BaseModel):
|
|
242
|
+
conversation_id: int
|
|
243
|
+
message: AIMessageOut
|
|
244
|
+
configured: bool
|
|
245
|
+
sources: list[dict[str, Any]] = Field(default_factory=list)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class AIConversationOut(BaseModel):
|
|
249
|
+
id: int
|
|
250
|
+
repository_id: int | None
|
|
251
|
+
title: str
|
|
252
|
+
created_at: str
|
|
253
|
+
updated_at: str
|
|
254
|
+
messages: list[AIMessageOut] = Field(default_factory=list)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class AIReviewRequest(BaseModel):
|
|
258
|
+
kind: Literal["analyst", "explain", "impact"] = "analyst"
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class AIReviewOut(BaseModel):
|
|
262
|
+
finding_id: int
|
|
263
|
+
configured: bool
|
|
264
|
+
provider: str | None = None
|
|
265
|
+
model: str | None = None
|
|
266
|
+
error: str | None = None
|
|
267
|
+
content: dict[str, Any] = Field(default_factory=dict)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class RemediationProposeRequest(BaseModel):
|
|
271
|
+
finding_id: int
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
class RemediationOut(BaseModel):
|
|
275
|
+
id: int
|
|
276
|
+
finding_id: int | None
|
|
277
|
+
repository_id: int
|
|
278
|
+
status: str
|
|
279
|
+
created_at: str
|
|
280
|
+
updated_at: str
|
|
281
|
+
provider: str | None = None
|
|
282
|
+
model: str | None = None
|
|
283
|
+
file: str | None = None
|
|
284
|
+
original: str | None = None
|
|
285
|
+
proposed: str | None = None
|
|
286
|
+
diff: str | None = None
|
|
287
|
+
rationale: str | None = None
|
|
288
|
+
error: str | None = None
|
|
289
|
+
history: list[dict[str, Any]] = Field(default_factory=list)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class ApproveRequest(BaseModel):
|
|
293
|
+
confirm: bool = Field(description="Must be true; the user acknowledges the patch is applied to the working tree.")
|
|
294
|
+
run_tests: bool = False
|
|
295
|
+
explicit_permission: bool = False
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class TestGenerateRequest(BaseModel):
|
|
299
|
+
finding_id: int
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
class TestGenerateOut(BaseModel):
|
|
303
|
+
finding_id: int
|
|
304
|
+
configured: bool
|
|
305
|
+
file_path: str | None = None
|
|
306
|
+
content: str | None = None
|
|
307
|
+
error: str | None = None
|
|
308
|
+
note: str = ""
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
# --------------------------------------------------------------------- architecture
|
|
312
|
+
class ArchitectureNode(BaseModel):
|
|
313
|
+
id: str
|
|
314
|
+
kind: str
|
|
315
|
+
label: str
|
|
316
|
+
files: list[str] = Field(default_factory=list)
|
|
317
|
+
confidence: str = "static"
|
|
318
|
+
details: dict[str, Any] = Field(default_factory=dict)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
class ArchitectureEdge(BaseModel):
|
|
322
|
+
source: str
|
|
323
|
+
target: str
|
|
324
|
+
kind: str
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
class ArchitectureOut(BaseModel):
|
|
328
|
+
repository_id: int
|
|
329
|
+
nodes: list[ArchitectureNode]
|
|
330
|
+
edges: list[ArchitectureEdge]
|
|
331
|
+
findings_by_node: dict[str, list[dict[str, Any]]] = Field(default_factory=dict)
|
|
332
|
+
notes: list[str] = Field(default_factory=list)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
# ------------------------------------------------------------------- dependencies
|
|
336
|
+
class DependencyOut(BaseModel):
|
|
337
|
+
name: str
|
|
338
|
+
version: str | None = None
|
|
339
|
+
direct: bool = True
|
|
340
|
+
license: str | None = None
|
|
341
|
+
advisories: list[dict[str, Any]] = Field(default_factory=list)
|
|
342
|
+
status: str = "unknown"
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
class DependenciesOut(BaseModel):
|
|
346
|
+
repository_id: int
|
|
347
|
+
package_manager: str | None = None
|
|
348
|
+
lockfiles: list[str] = Field(default_factory=list)
|
|
349
|
+
dependencies: list[DependencyOut] = Field(default_factory=list)
|
|
350
|
+
notes: list[str] = Field(default_factory=list)
|
|
351
|
+
osv_checked: bool = False
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
# ----------------------------------------------------------------------- settings
|
|
355
|
+
class AISettingsIn(BaseModel):
|
|
356
|
+
enabled: bool = False
|
|
357
|
+
provider: str = Field(default="openrouter", max_length=60)
|
|
358
|
+
base_url: str = Field(default="https://openrouter.ai/api/v1", max_length=300)
|
|
359
|
+
model: str = Field(default="", max_length=200)
|
|
360
|
+
timeout: int = Field(default=30, ge=1, le=600)
|
|
361
|
+
max_output_tokens: int = Field(default=3000, ge=1, le=16000)
|
|
362
|
+
share_source_context: bool = False
|
|
363
|
+
# Optional. Held in process memory only; never persisted or echoed back.
|
|
364
|
+
# An empty string means "leave the existing key unchanged"; use clear_api_key
|
|
365
|
+
# to remove it.
|
|
366
|
+
api_key: str = Field(default="", max_length=512)
|
|
367
|
+
clear_api_key: bool = False
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
class AISettingsOut(BaseModel):
|
|
371
|
+
enabled: bool
|
|
372
|
+
provider: str
|
|
373
|
+
base_url: str
|
|
374
|
+
model: str
|
|
375
|
+
timeout: int
|
|
376
|
+
max_output_tokens: int
|
|
377
|
+
share_source_context: bool
|
|
378
|
+
api_key_configured: bool
|
|
379
|
+
source: Literal["environment", "settings", "none"]
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
class ScanSettingsIn(BaseModel):
|
|
383
|
+
default_scan_profile: str = "standard"
|
|
384
|
+
allow_test_execution: bool = False
|
|
385
|
+
test_image: str = Field(default="python:3.12-slim", max_length=200)
|
|
386
|
+
test_timeout: int = Field(default=300, ge=1, le=7200)
|
|
387
|
+
max_memory_mb: int = Field(default=512, ge=64, le=16384)
|
|
388
|
+
max_cpu_seconds: int = Field(default=60, ge=1, le=3600)
|
|
389
|
+
max_snapshot_mb: int = Field(default=256, ge=1, le=2048)
|
|
390
|
+
max_write_mb: int = Field(default=64, ge=2, le=2048)
|
|
391
|
+
osv_enabled: bool = True
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
class ScanSettingsOut(ScanSettingsIn):
|
|
395
|
+
pass
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
class AIConnectionTestOut(BaseModel):
|
|
399
|
+
ok: bool
|
|
400
|
+
configured: bool
|
|
401
|
+
provider: str | None = None
|
|
402
|
+
model: str | None = None
|
|
403
|
+
detail: str
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
class ConversationOut(BaseModel):
|
|
407
|
+
id: int
|
|
408
|
+
repository_id: int | None = None
|
|
409
|
+
title: str
|
|
410
|
+
created_at: str
|
|
411
|
+
updated_at: str
|
|
412
|
+
message_count: int = 0
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
class SummaryRequest(BaseModel):
|
|
416
|
+
repository_id: int | None = None
|
|
417
|
+
scan_id: int | None = None
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
class SummaryOut(BaseModel):
|
|
421
|
+
configured: bool
|
|
422
|
+
provider: str | None = None
|
|
423
|
+
model: str | None = None
|
|
424
|
+
error: str | None = None
|
|
425
|
+
text: str = ""
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
class ThreatModelRequest(BaseModel):
|
|
429
|
+
repository_id: int
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
class ThreatModelOut(BaseModel):
|
|
433
|
+
repository_id: int
|
|
434
|
+
configured: bool
|
|
435
|
+
provider: str | None = None
|
|
436
|
+
model: str | None = None
|
|
437
|
+
error: str | None = None
|
|
438
|
+
text: str = ""
|
|
439
|
+
note: str = ""
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
class HealthOut(BaseModel):
|
|
443
|
+
status: str
|
|
444
|
+
version: str
|
|
445
|
+
database: str
|
|
446
|
+
ai_configured: bool
|
|
447
|
+
repositories: int
|
|
448
|
+
scans: int
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
class GenericMessage(BaseModel):
|
|
452
|
+
detail: str
|