superqode 0.1.5__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 (288) hide show
  1. superqode/__init__.py +33 -0
  2. superqode/acp/__init__.py +23 -0
  3. superqode/acp/client.py +913 -0
  4. superqode/acp/permission_screen.py +457 -0
  5. superqode/acp/types.py +480 -0
  6. superqode/acp_discovery.py +856 -0
  7. superqode/agent/__init__.py +22 -0
  8. superqode/agent/edit_strategies.py +334 -0
  9. superqode/agent/loop.py +892 -0
  10. superqode/agent/qe_report_templates.py +39 -0
  11. superqode/agent/system_prompts.py +353 -0
  12. superqode/agent_output.py +721 -0
  13. superqode/agent_stream.py +953 -0
  14. superqode/agents/__init__.py +59 -0
  15. superqode/agents/acp_registry.py +305 -0
  16. superqode/agents/client.py +249 -0
  17. superqode/agents/data/augmentcode.com.toml +51 -0
  18. superqode/agents/data/cagent.dev.toml +51 -0
  19. superqode/agents/data/claude.com.toml +60 -0
  20. superqode/agents/data/codeassistant.dev.toml +51 -0
  21. superqode/agents/data/codex.openai.com.toml +57 -0
  22. superqode/agents/data/fastagent.ai.toml +66 -0
  23. superqode/agents/data/geminicli.com.toml +77 -0
  24. superqode/agents/data/goose.block.xyz.toml +54 -0
  25. superqode/agents/data/junie.jetbrains.com.toml +56 -0
  26. superqode/agents/data/kimi.moonshot.cn.toml +57 -0
  27. superqode/agents/data/llmlingagent.dev.toml +51 -0
  28. superqode/agents/data/molt.bot.toml +49 -0
  29. superqode/agents/data/opencode.ai.toml +60 -0
  30. superqode/agents/data/stakpak.dev.toml +51 -0
  31. superqode/agents/data/vtcode.dev.toml +51 -0
  32. superqode/agents/discovery.py +266 -0
  33. superqode/agents/messaging.py +160 -0
  34. superqode/agents/persona.py +166 -0
  35. superqode/agents/registry.py +421 -0
  36. superqode/agents/schema.py +72 -0
  37. superqode/agents/unified.py +367 -0
  38. superqode/app/__init__.py +111 -0
  39. superqode/app/constants.py +314 -0
  40. superqode/app/css.py +366 -0
  41. superqode/app/models.py +118 -0
  42. superqode/app/suggester.py +125 -0
  43. superqode/app/widgets.py +1591 -0
  44. superqode/app_enhanced.py +399 -0
  45. superqode/app_main.py +17187 -0
  46. superqode/approval.py +312 -0
  47. superqode/atomic.py +296 -0
  48. superqode/commands/__init__.py +1 -0
  49. superqode/commands/acp.py +965 -0
  50. superqode/commands/agents.py +180 -0
  51. superqode/commands/auth.py +278 -0
  52. superqode/commands/config.py +374 -0
  53. superqode/commands/init.py +826 -0
  54. superqode/commands/providers.py +819 -0
  55. superqode/commands/qe.py +1145 -0
  56. superqode/commands/roles.py +380 -0
  57. superqode/commands/serve.py +172 -0
  58. superqode/commands/suggestions.py +127 -0
  59. superqode/commands/superqe.py +460 -0
  60. superqode/config/__init__.py +51 -0
  61. superqode/config/loader.py +812 -0
  62. superqode/config/schema.py +498 -0
  63. superqode/core/__init__.py +111 -0
  64. superqode/core/roles.py +281 -0
  65. superqode/danger.py +386 -0
  66. superqode/data/superqode-template.yaml +1522 -0
  67. superqode/design_system.py +1080 -0
  68. superqode/dialogs/__init__.py +6 -0
  69. superqode/dialogs/base.py +39 -0
  70. superqode/dialogs/model.py +130 -0
  71. superqode/dialogs/provider.py +870 -0
  72. superqode/diff_view.py +919 -0
  73. superqode/enterprise.py +21 -0
  74. superqode/evaluation/__init__.py +25 -0
  75. superqode/evaluation/adapters.py +93 -0
  76. superqode/evaluation/behaviors.py +89 -0
  77. superqode/evaluation/engine.py +209 -0
  78. superqode/evaluation/scenarios.py +96 -0
  79. superqode/execution/__init__.py +36 -0
  80. superqode/execution/linter.py +538 -0
  81. superqode/execution/modes.py +347 -0
  82. superqode/execution/resolver.py +283 -0
  83. superqode/execution/runner.py +642 -0
  84. superqode/file_explorer.py +811 -0
  85. superqode/file_viewer.py +471 -0
  86. superqode/flash.py +183 -0
  87. superqode/guidance/__init__.py +58 -0
  88. superqode/guidance/config.py +203 -0
  89. superqode/guidance/prompts.py +71 -0
  90. superqode/harness/__init__.py +54 -0
  91. superqode/harness/accelerator.py +291 -0
  92. superqode/harness/config.py +319 -0
  93. superqode/harness/validator.py +147 -0
  94. superqode/history.py +279 -0
  95. superqode/integrations/superopt_runner.py +124 -0
  96. superqode/logging/__init__.py +49 -0
  97. superqode/logging/adapters.py +219 -0
  98. superqode/logging/formatter.py +923 -0
  99. superqode/logging/integration.py +341 -0
  100. superqode/logging/sinks.py +170 -0
  101. superqode/logging/unified_log.py +417 -0
  102. superqode/lsp/__init__.py +26 -0
  103. superqode/lsp/client.py +544 -0
  104. superqode/main.py +1069 -0
  105. superqode/mcp/__init__.py +89 -0
  106. superqode/mcp/auth_storage.py +380 -0
  107. superqode/mcp/client.py +1236 -0
  108. superqode/mcp/config.py +319 -0
  109. superqode/mcp/integration.py +337 -0
  110. superqode/mcp/oauth.py +436 -0
  111. superqode/mcp/oauth_callback.py +385 -0
  112. superqode/mcp/types.py +290 -0
  113. superqode/memory/__init__.py +31 -0
  114. superqode/memory/feedback.py +342 -0
  115. superqode/memory/store.py +522 -0
  116. superqode/notifications.py +369 -0
  117. superqode/optimization/__init__.py +5 -0
  118. superqode/optimization/config.py +33 -0
  119. superqode/permissions/__init__.py +25 -0
  120. superqode/permissions/rules.py +488 -0
  121. superqode/plan.py +323 -0
  122. superqode/providers/__init__.py +33 -0
  123. superqode/providers/gateway/__init__.py +165 -0
  124. superqode/providers/gateway/base.py +228 -0
  125. superqode/providers/gateway/litellm_gateway.py +1170 -0
  126. superqode/providers/gateway/openresponses_gateway.py +436 -0
  127. superqode/providers/health.py +297 -0
  128. superqode/providers/huggingface/__init__.py +74 -0
  129. superqode/providers/huggingface/downloader.py +472 -0
  130. superqode/providers/huggingface/endpoints.py +442 -0
  131. superqode/providers/huggingface/hub.py +531 -0
  132. superqode/providers/huggingface/inference.py +394 -0
  133. superqode/providers/huggingface/transformers_runner.py +516 -0
  134. superqode/providers/local/__init__.py +100 -0
  135. superqode/providers/local/base.py +438 -0
  136. superqode/providers/local/discovery.py +418 -0
  137. superqode/providers/local/lmstudio.py +256 -0
  138. superqode/providers/local/mlx.py +457 -0
  139. superqode/providers/local/ollama.py +486 -0
  140. superqode/providers/local/sglang.py +268 -0
  141. superqode/providers/local/tgi.py +260 -0
  142. superqode/providers/local/tool_support.py +477 -0
  143. superqode/providers/local/vllm.py +258 -0
  144. superqode/providers/manager.py +1338 -0
  145. superqode/providers/models.py +1016 -0
  146. superqode/providers/models_dev.py +578 -0
  147. superqode/providers/openresponses/__init__.py +87 -0
  148. superqode/providers/openresponses/converters/__init__.py +17 -0
  149. superqode/providers/openresponses/converters/messages.py +343 -0
  150. superqode/providers/openresponses/converters/tools.py +268 -0
  151. superqode/providers/openresponses/schema/__init__.py +56 -0
  152. superqode/providers/openresponses/schema/models.py +585 -0
  153. superqode/providers/openresponses/streaming/__init__.py +5 -0
  154. superqode/providers/openresponses/streaming/parser.py +338 -0
  155. superqode/providers/openresponses/tools/__init__.py +21 -0
  156. superqode/providers/openresponses/tools/apply_patch.py +352 -0
  157. superqode/providers/openresponses/tools/code_interpreter.py +290 -0
  158. superqode/providers/openresponses/tools/file_search.py +333 -0
  159. superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
  160. superqode/providers/registry.py +716 -0
  161. superqode/providers/usage.py +332 -0
  162. superqode/pure_mode.py +384 -0
  163. superqode/qr/__init__.py +23 -0
  164. superqode/qr/dashboard.py +781 -0
  165. superqode/qr/generator.py +1018 -0
  166. superqode/qr/templates.py +135 -0
  167. superqode/safety/__init__.py +41 -0
  168. superqode/safety/sandbox.py +413 -0
  169. superqode/safety/warnings.py +256 -0
  170. superqode/server/__init__.py +33 -0
  171. superqode/server/lsp_server.py +775 -0
  172. superqode/server/web.py +250 -0
  173. superqode/session/__init__.py +25 -0
  174. superqode/session/persistence.py +580 -0
  175. superqode/session/sharing.py +477 -0
  176. superqode/session.py +475 -0
  177. superqode/sidebar.py +2991 -0
  178. superqode/stream_view.py +648 -0
  179. superqode/styles/__init__.py +3 -0
  180. superqode/superqe/__init__.py +184 -0
  181. superqode/superqe/acp_runner.py +1064 -0
  182. superqode/superqe/constitution/__init__.py +62 -0
  183. superqode/superqe/constitution/evaluator.py +308 -0
  184. superqode/superqe/constitution/loader.py +432 -0
  185. superqode/superqe/constitution/schema.py +250 -0
  186. superqode/superqe/events.py +591 -0
  187. superqode/superqe/frameworks/__init__.py +65 -0
  188. superqode/superqe/frameworks/base.py +234 -0
  189. superqode/superqe/frameworks/e2e.py +263 -0
  190. superqode/superqe/frameworks/executor.py +237 -0
  191. superqode/superqe/frameworks/javascript.py +409 -0
  192. superqode/superqe/frameworks/python.py +373 -0
  193. superqode/superqe/frameworks/registry.py +92 -0
  194. superqode/superqe/mcp_tools/__init__.py +47 -0
  195. superqode/superqe/mcp_tools/core_tools.py +418 -0
  196. superqode/superqe/mcp_tools/registry.py +230 -0
  197. superqode/superqe/mcp_tools/testing_tools.py +167 -0
  198. superqode/superqe/noise.py +89 -0
  199. superqode/superqe/orchestrator.py +778 -0
  200. superqode/superqe/roles.py +609 -0
  201. superqode/superqe/session.py +713 -0
  202. superqode/superqe/skills/__init__.py +57 -0
  203. superqode/superqe/skills/base.py +106 -0
  204. superqode/superqe/skills/core_skills.py +899 -0
  205. superqode/superqe/skills/registry.py +90 -0
  206. superqode/superqe/verifier.py +101 -0
  207. superqode/superqe_cli.py +76 -0
  208. superqode/tool_call.py +358 -0
  209. superqode/tools/__init__.py +93 -0
  210. superqode/tools/agent_tools.py +496 -0
  211. superqode/tools/base.py +324 -0
  212. superqode/tools/batch_tool.py +133 -0
  213. superqode/tools/diagnostics.py +311 -0
  214. superqode/tools/edit_tools.py +653 -0
  215. superqode/tools/enhanced_base.py +515 -0
  216. superqode/tools/file_tools.py +269 -0
  217. superqode/tools/file_tracking.py +45 -0
  218. superqode/tools/lsp_tools.py +610 -0
  219. superqode/tools/network_tools.py +350 -0
  220. superqode/tools/permissions.py +400 -0
  221. superqode/tools/question_tool.py +324 -0
  222. superqode/tools/search_tools.py +598 -0
  223. superqode/tools/shell_tools.py +259 -0
  224. superqode/tools/todo_tools.py +121 -0
  225. superqode/tools/validation.py +80 -0
  226. superqode/tools/web_tools.py +639 -0
  227. superqode/tui.py +1152 -0
  228. superqode/tui_integration.py +875 -0
  229. superqode/tui_widgets/__init__.py +27 -0
  230. superqode/tui_widgets/widgets/__init__.py +18 -0
  231. superqode/tui_widgets/widgets/progress.py +185 -0
  232. superqode/tui_widgets/widgets/tool_display.py +188 -0
  233. superqode/undo_manager.py +574 -0
  234. superqode/utils/__init__.py +5 -0
  235. superqode/utils/error_handling.py +323 -0
  236. superqode/utils/fuzzy.py +257 -0
  237. superqode/widgets/__init__.py +477 -0
  238. superqode/widgets/agent_collab.py +390 -0
  239. superqode/widgets/agent_store.py +936 -0
  240. superqode/widgets/agent_switcher.py +395 -0
  241. superqode/widgets/animation_manager.py +284 -0
  242. superqode/widgets/code_context.py +356 -0
  243. superqode/widgets/command_palette.py +412 -0
  244. superqode/widgets/connection_status.py +537 -0
  245. superqode/widgets/conversation_history.py +470 -0
  246. superqode/widgets/diff_indicator.py +155 -0
  247. superqode/widgets/enhanced_status_bar.py +385 -0
  248. superqode/widgets/enhanced_toast.py +476 -0
  249. superqode/widgets/file_browser.py +809 -0
  250. superqode/widgets/file_reference.py +585 -0
  251. superqode/widgets/issue_timeline.py +340 -0
  252. superqode/widgets/leader_key.py +264 -0
  253. superqode/widgets/mode_switcher.py +445 -0
  254. superqode/widgets/model_picker.py +234 -0
  255. superqode/widgets/permission_preview.py +1205 -0
  256. superqode/widgets/prompt.py +358 -0
  257. superqode/widgets/provider_connect.py +725 -0
  258. superqode/widgets/pty_shell.py +587 -0
  259. superqode/widgets/qe_dashboard.py +321 -0
  260. superqode/widgets/resizable_sidebar.py +377 -0
  261. superqode/widgets/response_changes.py +218 -0
  262. superqode/widgets/response_display.py +528 -0
  263. superqode/widgets/rich_tool_display.py +613 -0
  264. superqode/widgets/sidebar_panels.py +1180 -0
  265. superqode/widgets/slash_complete.py +356 -0
  266. superqode/widgets/split_view.py +612 -0
  267. superqode/widgets/status_bar.py +273 -0
  268. superqode/widgets/superqode_display.py +786 -0
  269. superqode/widgets/thinking_display.py +815 -0
  270. superqode/widgets/throbber.py +87 -0
  271. superqode/widgets/toast.py +206 -0
  272. superqode/widgets/unified_output.py +1073 -0
  273. superqode/workspace/__init__.py +75 -0
  274. superqode/workspace/artifacts.py +472 -0
  275. superqode/workspace/coordinator.py +353 -0
  276. superqode/workspace/diff_tracker.py +429 -0
  277. superqode/workspace/git_guard.py +373 -0
  278. superqode/workspace/git_snapshot.py +526 -0
  279. superqode/workspace/manager.py +750 -0
  280. superqode/workspace/snapshot.py +357 -0
  281. superqode/workspace/watcher.py +535 -0
  282. superqode/workspace/worktree.py +440 -0
  283. superqode-0.1.5.dist-info/METADATA +204 -0
  284. superqode-0.1.5.dist-info/RECORD +288 -0
  285. superqode-0.1.5.dist-info/WHEEL +5 -0
  286. superqode-0.1.5.dist-info/entry_points.txt +3 -0
  287. superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
  288. superqode-0.1.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,342 @@
1
+ """
2
+ Feedback Collection - Collect user validation for findings.
3
+
4
+ Enables users to mark findings as:
5
+ - Valid (true positive)
6
+ - False positive (suppress in future)
7
+ - Fixed (can learn fix pattern)
8
+
9
+ This feedback improves future QE runs by:
10
+ - Reducing false positives via suppressions
11
+ - Improving role accuracy metrics
12
+ - Learning successful fix patterns
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from dataclasses import dataclass
18
+ from datetime import datetime
19
+ from enum import Enum
20
+ from pathlib import Path
21
+ from typing import Any, Dict, List, Optional
22
+ import logging
23
+
24
+ from .store import MemoryStore, Suppression
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ class FeedbackType(Enum):
30
+ """Types of feedback for findings."""
31
+
32
+ VALID = "valid" # True positive, confirmed issue
33
+ FALSE_POSITIVE = "false_positive" # Should be suppressed
34
+ FIXED = "fixed" # Issue was fixed
35
+ WONT_FIX = "wont_fix" # Acknowledged but won't fix
36
+ DUPLICATE = "duplicate" # Same as another finding
37
+
38
+
39
+ @dataclass
40
+ class FindingFeedback:
41
+ """Feedback for a specific finding."""
42
+
43
+ finding_id: str
44
+ finding_title: str
45
+ feedback_type: FeedbackType
46
+ reason: str
47
+ created_at: str
48
+ created_by: str
49
+
50
+ # For false positives
51
+ suppress_scope: str = "project" # "project", "team", or "global"
52
+ suppress_pattern_type: str = "fingerprint" # How to match in future
53
+
54
+ # For fixes
55
+ fix_description: Optional[str] = None
56
+ patch_file: Optional[str] = None
57
+
58
+ # For duplicates
59
+ duplicate_of: Optional[str] = None
60
+
61
+ def to_dict(self) -> Dict[str, Any]:
62
+ return {
63
+ "finding_id": self.finding_id,
64
+ "finding_title": self.finding_title,
65
+ "feedback_type": self.feedback_type.value,
66
+ "reason": self.reason,
67
+ "created_at": self.created_at,
68
+ "created_by": self.created_by,
69
+ "suppress_scope": self.suppress_scope,
70
+ "suppress_pattern_type": self.suppress_pattern_type,
71
+ "fix_description": self.fix_description,
72
+ "patch_file": self.patch_file,
73
+ "duplicate_of": self.duplicate_of,
74
+ }
75
+
76
+
77
+ class FeedbackCollector:
78
+ """
79
+ Collects and processes user feedback on findings.
80
+
81
+ Integrates with MemoryStore to persist learnings.
82
+ """
83
+
84
+ def __init__(self, project_root: Path, enable_ml: bool = False):
85
+ self.project_root = project_root
86
+ self.memory_store = MemoryStore(project_root)
87
+ self._pending_feedback: List[FindingFeedback] = []
88
+ self._predictor = None
89
+
90
+ if enable_ml:
91
+ logger.debug("ML predictor not available in OSS build")
92
+
93
+ def mark_valid(
94
+ self,
95
+ finding_id: str,
96
+ finding_title: str,
97
+ category: str,
98
+ severity: str,
99
+ role_name: str,
100
+ reason: str = "",
101
+ ) -> FindingFeedback:
102
+ """Mark a finding as a valid true positive."""
103
+ import os
104
+
105
+ feedback = FindingFeedback(
106
+ finding_id=finding_id,
107
+ finding_title=finding_title,
108
+ feedback_type=FeedbackType.VALID,
109
+ reason=reason or "Confirmed as valid issue",
110
+ created_at=datetime.now().isoformat(),
111
+ created_by=os.environ.get("USER", "unknown"),
112
+ )
113
+
114
+ # Update role metrics
115
+ memory = self.memory_store.load()
116
+ if role_name in memory.role_metrics:
117
+ memory.role_metrics[role_name].confirmed_findings += 1
118
+ memory.role_metrics[role_name].update_accuracy()
119
+
120
+ self.memory_store.save()
121
+ self._pending_feedback.append(feedback)
122
+
123
+ logger.info(f"Marked finding {finding_id} as valid")
124
+ return feedback
125
+
126
+ def mark_false_positive(
127
+ self,
128
+ finding_id: str,
129
+ finding_title: str,
130
+ finding_fingerprint: Optional[str],
131
+ role_name: str,
132
+ reason: str,
133
+ scope: str = "project",
134
+ pattern_type: str = "fingerprint",
135
+ expires_in_days: Optional[int] = None,
136
+ ) -> tuple:
137
+ """
138
+ Mark a finding as a false positive and create suppression.
139
+
140
+ Returns:
141
+ Tuple of (FindingFeedback, Suppression)
142
+ """
143
+ import os
144
+
145
+ feedback = FindingFeedback(
146
+ finding_id=finding_id,
147
+ finding_title=finding_title,
148
+ feedback_type=FeedbackType.FALSE_POSITIVE,
149
+ reason=reason,
150
+ created_at=datetime.now().isoformat(),
151
+ created_by=os.environ.get("USER", "unknown"),
152
+ suppress_scope=scope,
153
+ suppress_pattern_type=pattern_type,
154
+ )
155
+
156
+ # Determine pattern to suppress
157
+ if pattern_type == "fingerprint" and finding_fingerprint:
158
+ pattern = finding_fingerprint
159
+ elif pattern_type == "title":
160
+ pattern = finding_title
161
+ else:
162
+ pattern = finding_fingerprint or finding_title
163
+
164
+ # Create suppression
165
+ suppression = self.memory_store.add_suppression(
166
+ pattern=pattern,
167
+ pattern_type=pattern_type,
168
+ reason=reason,
169
+ scope=scope,
170
+ expires_in_days=expires_in_days,
171
+ )
172
+
173
+ # Update role metrics
174
+ memory = self.memory_store.load()
175
+ if role_name in memory.role_metrics:
176
+ memory.role_metrics[role_name].false_positives += 1
177
+ memory.role_metrics[role_name].update_accuracy()
178
+
179
+ self.memory_store.save(to_team=(scope == "team"))
180
+ self._pending_feedback.append(feedback)
181
+
182
+ logger.info(
183
+ f"Marked finding {finding_id} as false positive, created suppression {suppression.id}"
184
+ )
185
+ return feedback, suppression
186
+
187
+ def mark_fixed(
188
+ self,
189
+ finding_id: str,
190
+ finding_title: str,
191
+ finding_fingerprint: Optional[str],
192
+ fix_description: str,
193
+ patch_file: Optional[str] = None,
194
+ ) -> FindingFeedback:
195
+ """Mark a finding as fixed and optionally record the fix pattern."""
196
+ import os
197
+ import hashlib
198
+
199
+ feedback = FindingFeedback(
200
+ finding_id=finding_id,
201
+ finding_title=finding_title,
202
+ feedback_type=FeedbackType.FIXED,
203
+ reason="Issue was fixed",
204
+ created_at=datetime.now().isoformat(),
205
+ created_by=os.environ.get("USER", "unknown"),
206
+ fix_description=fix_description,
207
+ patch_file=patch_file,
208
+ )
209
+
210
+ # Record fix pattern if we have details
211
+ if finding_fingerprint and fix_description:
212
+ from .store import FixPattern
213
+
214
+ memory = self.memory_store.load()
215
+ fix_id = hashlib.sha256(
216
+ f"{finding_fingerprint}:{datetime.now().isoformat()}".encode()
217
+ ).hexdigest()[:12]
218
+
219
+ fix_pattern = FixPattern(
220
+ id=fix_id,
221
+ issue_fingerprint=finding_fingerprint,
222
+ issue_title=finding_title,
223
+ fix_description=fix_description,
224
+ patch_template=self._read_patch(patch_file) if patch_file else None,
225
+ created_at=datetime.now().isoformat(),
226
+ )
227
+ memory.fix_patterns.append(fix_pattern)
228
+ self.memory_store.save()
229
+
230
+ self._pending_feedback.append(feedback)
231
+ logger.info(f"Marked finding {finding_id} as fixed")
232
+ return feedback
233
+
234
+ def mark_wont_fix(
235
+ self,
236
+ finding_id: str,
237
+ finding_title: str,
238
+ reason: str,
239
+ ) -> FindingFeedback:
240
+ """Mark a finding as acknowledged but won't fix."""
241
+ import os
242
+
243
+ feedback = FindingFeedback(
244
+ finding_id=finding_id,
245
+ finding_title=finding_title,
246
+ feedback_type=FeedbackType.WONT_FIX,
247
+ reason=reason,
248
+ created_at=datetime.now().isoformat(),
249
+ created_by=os.environ.get("USER", "unknown"),
250
+ )
251
+
252
+ self._pending_feedback.append(feedback)
253
+ logger.info(f"Marked finding {finding_id} as won't fix: {reason}")
254
+ return feedback
255
+
256
+ def mark_duplicate(
257
+ self,
258
+ finding_id: str,
259
+ finding_title: str,
260
+ duplicate_of: str,
261
+ ) -> FindingFeedback:
262
+ """Mark a finding as a duplicate of another."""
263
+ import os
264
+
265
+ feedback = FindingFeedback(
266
+ finding_id=finding_id,
267
+ finding_title=finding_title,
268
+ feedback_type=FeedbackType.DUPLICATE,
269
+ reason=f"Duplicate of {duplicate_of}",
270
+ created_at=datetime.now().isoformat(),
271
+ created_by=os.environ.get("USER", "unknown"),
272
+ duplicate_of=duplicate_of,
273
+ )
274
+
275
+ self._pending_feedback.append(feedback)
276
+ logger.info(f"Marked finding {finding_id} as duplicate of {duplicate_of}")
277
+ return feedback
278
+
279
+ def _read_patch(self, patch_file: str) -> Optional[str]:
280
+ """Read patch content if file exists."""
281
+ try:
282
+ path = Path(patch_file)
283
+ if path.exists():
284
+ return path.read_text()
285
+ # Try relative to project
286
+ path = self.project_root / patch_file
287
+ if path.exists():
288
+ return path.read_text()
289
+ except Exception as e:
290
+ logger.warning(f"Could not read patch file: {e}")
291
+ return None
292
+
293
+ def get_pending_feedback(self) -> List[FindingFeedback]:
294
+ """Get feedback collected in this session."""
295
+ return self._pending_feedback.copy()
296
+
297
+ def clear_pending(self) -> None:
298
+ """Clear pending feedback after processing."""
299
+ self._pending_feedback.clear()
300
+
301
+ def get_role_accuracy(self, role_name: str) -> Optional[float]:
302
+ """Get accuracy rate for a role."""
303
+ memory = self.memory_store.load()
304
+ if role_name in memory.role_metrics:
305
+ return memory.role_metrics[role_name].accuracy_rate
306
+ return None
307
+
308
+ def _add_ml_training(
309
+ self,
310
+ finding_id: str,
311
+ finding_title: str,
312
+ severity: str,
313
+ is_true_positive: bool,
314
+ ) -> None:
315
+ """Add feedback to ML predictor training data."""
316
+ if not self._predictor:
317
+ return
318
+
319
+ # Create a minimal finding-like object for the predictor
320
+ return None
321
+
322
+ def get_ml_stats(self) -> Optional[Dict[str, Any]]:
323
+ """Get ML predictor statistics (not available in OSS build)."""
324
+ return None
325
+
326
+ def get_suppression_stats(self) -> Dict[str, Any]:
327
+ """Get statistics about suppressions."""
328
+ memory = self.memory_store.load()
329
+ active = memory.get_active_suppressions()
330
+ by_scope = {"project": 0, "team": 0, "global": 0}
331
+ by_type = {"title": 0, "rule_id": 0, "fingerprint": 0, "file_pattern": 0}
332
+
333
+ for supp in active:
334
+ by_scope[supp.scope] = by_scope.get(supp.scope, 0) + 1
335
+ by_type[supp.pattern_type] = by_type.get(supp.pattern_type, 0) + 1
336
+
337
+ return {
338
+ "total_active": len(active),
339
+ "total_applied": memory.total_suppressions_applied,
340
+ "by_scope": by_scope,
341
+ "by_type": by_type,
342
+ }