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.
- superqode/__init__.py +33 -0
- superqode/acp/__init__.py +23 -0
- superqode/acp/client.py +913 -0
- superqode/acp/permission_screen.py +457 -0
- superqode/acp/types.py +480 -0
- superqode/acp_discovery.py +856 -0
- superqode/agent/__init__.py +22 -0
- superqode/agent/edit_strategies.py +334 -0
- superqode/agent/loop.py +892 -0
- superqode/agent/qe_report_templates.py +39 -0
- superqode/agent/system_prompts.py +353 -0
- superqode/agent_output.py +721 -0
- superqode/agent_stream.py +953 -0
- superqode/agents/__init__.py +59 -0
- superqode/agents/acp_registry.py +305 -0
- superqode/agents/client.py +249 -0
- superqode/agents/data/augmentcode.com.toml +51 -0
- superqode/agents/data/cagent.dev.toml +51 -0
- superqode/agents/data/claude.com.toml +60 -0
- superqode/agents/data/codeassistant.dev.toml +51 -0
- superqode/agents/data/codex.openai.com.toml +57 -0
- superqode/agents/data/fastagent.ai.toml +66 -0
- superqode/agents/data/geminicli.com.toml +77 -0
- superqode/agents/data/goose.block.xyz.toml +54 -0
- superqode/agents/data/junie.jetbrains.com.toml +56 -0
- superqode/agents/data/kimi.moonshot.cn.toml +57 -0
- superqode/agents/data/llmlingagent.dev.toml +51 -0
- superqode/agents/data/molt.bot.toml +49 -0
- superqode/agents/data/opencode.ai.toml +60 -0
- superqode/agents/data/stakpak.dev.toml +51 -0
- superqode/agents/data/vtcode.dev.toml +51 -0
- superqode/agents/discovery.py +266 -0
- superqode/agents/messaging.py +160 -0
- superqode/agents/persona.py +166 -0
- superqode/agents/registry.py +421 -0
- superqode/agents/schema.py +72 -0
- superqode/agents/unified.py +367 -0
- superqode/app/__init__.py +111 -0
- superqode/app/constants.py +314 -0
- superqode/app/css.py +366 -0
- superqode/app/models.py +118 -0
- superqode/app/suggester.py +125 -0
- superqode/app/widgets.py +1591 -0
- superqode/app_enhanced.py +399 -0
- superqode/app_main.py +17187 -0
- superqode/approval.py +312 -0
- superqode/atomic.py +296 -0
- superqode/commands/__init__.py +1 -0
- superqode/commands/acp.py +965 -0
- superqode/commands/agents.py +180 -0
- superqode/commands/auth.py +278 -0
- superqode/commands/config.py +374 -0
- superqode/commands/init.py +826 -0
- superqode/commands/providers.py +819 -0
- superqode/commands/qe.py +1145 -0
- superqode/commands/roles.py +380 -0
- superqode/commands/serve.py +172 -0
- superqode/commands/suggestions.py +127 -0
- superqode/commands/superqe.py +460 -0
- superqode/config/__init__.py +51 -0
- superqode/config/loader.py +812 -0
- superqode/config/schema.py +498 -0
- superqode/core/__init__.py +111 -0
- superqode/core/roles.py +281 -0
- superqode/danger.py +386 -0
- superqode/data/superqode-template.yaml +1522 -0
- superqode/design_system.py +1080 -0
- superqode/dialogs/__init__.py +6 -0
- superqode/dialogs/base.py +39 -0
- superqode/dialogs/model.py +130 -0
- superqode/dialogs/provider.py +870 -0
- superqode/diff_view.py +919 -0
- superqode/enterprise.py +21 -0
- superqode/evaluation/__init__.py +25 -0
- superqode/evaluation/adapters.py +93 -0
- superqode/evaluation/behaviors.py +89 -0
- superqode/evaluation/engine.py +209 -0
- superqode/evaluation/scenarios.py +96 -0
- superqode/execution/__init__.py +36 -0
- superqode/execution/linter.py +538 -0
- superqode/execution/modes.py +347 -0
- superqode/execution/resolver.py +283 -0
- superqode/execution/runner.py +642 -0
- superqode/file_explorer.py +811 -0
- superqode/file_viewer.py +471 -0
- superqode/flash.py +183 -0
- superqode/guidance/__init__.py +58 -0
- superqode/guidance/config.py +203 -0
- superqode/guidance/prompts.py +71 -0
- superqode/harness/__init__.py +54 -0
- superqode/harness/accelerator.py +291 -0
- superqode/harness/config.py +319 -0
- superqode/harness/validator.py +147 -0
- superqode/history.py +279 -0
- superqode/integrations/superopt_runner.py +124 -0
- superqode/logging/__init__.py +49 -0
- superqode/logging/adapters.py +219 -0
- superqode/logging/formatter.py +923 -0
- superqode/logging/integration.py +341 -0
- superqode/logging/sinks.py +170 -0
- superqode/logging/unified_log.py +417 -0
- superqode/lsp/__init__.py +26 -0
- superqode/lsp/client.py +544 -0
- superqode/main.py +1069 -0
- superqode/mcp/__init__.py +89 -0
- superqode/mcp/auth_storage.py +380 -0
- superqode/mcp/client.py +1236 -0
- superqode/mcp/config.py +319 -0
- superqode/mcp/integration.py +337 -0
- superqode/mcp/oauth.py +436 -0
- superqode/mcp/oauth_callback.py +385 -0
- superqode/mcp/types.py +290 -0
- superqode/memory/__init__.py +31 -0
- superqode/memory/feedback.py +342 -0
- superqode/memory/store.py +522 -0
- superqode/notifications.py +369 -0
- superqode/optimization/__init__.py +5 -0
- superqode/optimization/config.py +33 -0
- superqode/permissions/__init__.py +25 -0
- superqode/permissions/rules.py +488 -0
- superqode/plan.py +323 -0
- superqode/providers/__init__.py +33 -0
- superqode/providers/gateway/__init__.py +165 -0
- superqode/providers/gateway/base.py +228 -0
- superqode/providers/gateway/litellm_gateway.py +1170 -0
- superqode/providers/gateway/openresponses_gateway.py +436 -0
- superqode/providers/health.py +297 -0
- superqode/providers/huggingface/__init__.py +74 -0
- superqode/providers/huggingface/downloader.py +472 -0
- superqode/providers/huggingface/endpoints.py +442 -0
- superqode/providers/huggingface/hub.py +531 -0
- superqode/providers/huggingface/inference.py +394 -0
- superqode/providers/huggingface/transformers_runner.py +516 -0
- superqode/providers/local/__init__.py +100 -0
- superqode/providers/local/base.py +438 -0
- superqode/providers/local/discovery.py +418 -0
- superqode/providers/local/lmstudio.py +256 -0
- superqode/providers/local/mlx.py +457 -0
- superqode/providers/local/ollama.py +486 -0
- superqode/providers/local/sglang.py +268 -0
- superqode/providers/local/tgi.py +260 -0
- superqode/providers/local/tool_support.py +477 -0
- superqode/providers/local/vllm.py +258 -0
- superqode/providers/manager.py +1338 -0
- superqode/providers/models.py +1016 -0
- superqode/providers/models_dev.py +578 -0
- superqode/providers/openresponses/__init__.py +87 -0
- superqode/providers/openresponses/converters/__init__.py +17 -0
- superqode/providers/openresponses/converters/messages.py +343 -0
- superqode/providers/openresponses/converters/tools.py +268 -0
- superqode/providers/openresponses/schema/__init__.py +56 -0
- superqode/providers/openresponses/schema/models.py +585 -0
- superqode/providers/openresponses/streaming/__init__.py +5 -0
- superqode/providers/openresponses/streaming/parser.py +338 -0
- superqode/providers/openresponses/tools/__init__.py +21 -0
- superqode/providers/openresponses/tools/apply_patch.py +352 -0
- superqode/providers/openresponses/tools/code_interpreter.py +290 -0
- superqode/providers/openresponses/tools/file_search.py +333 -0
- superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
- superqode/providers/registry.py +716 -0
- superqode/providers/usage.py +332 -0
- superqode/pure_mode.py +384 -0
- superqode/qr/__init__.py +23 -0
- superqode/qr/dashboard.py +781 -0
- superqode/qr/generator.py +1018 -0
- superqode/qr/templates.py +135 -0
- superqode/safety/__init__.py +41 -0
- superqode/safety/sandbox.py +413 -0
- superqode/safety/warnings.py +256 -0
- superqode/server/__init__.py +33 -0
- superqode/server/lsp_server.py +775 -0
- superqode/server/web.py +250 -0
- superqode/session/__init__.py +25 -0
- superqode/session/persistence.py +580 -0
- superqode/session/sharing.py +477 -0
- superqode/session.py +475 -0
- superqode/sidebar.py +2991 -0
- superqode/stream_view.py +648 -0
- superqode/styles/__init__.py +3 -0
- superqode/superqe/__init__.py +184 -0
- superqode/superqe/acp_runner.py +1064 -0
- superqode/superqe/constitution/__init__.py +62 -0
- superqode/superqe/constitution/evaluator.py +308 -0
- superqode/superqe/constitution/loader.py +432 -0
- superqode/superqe/constitution/schema.py +250 -0
- superqode/superqe/events.py +591 -0
- superqode/superqe/frameworks/__init__.py +65 -0
- superqode/superqe/frameworks/base.py +234 -0
- superqode/superqe/frameworks/e2e.py +263 -0
- superqode/superqe/frameworks/executor.py +237 -0
- superqode/superqe/frameworks/javascript.py +409 -0
- superqode/superqe/frameworks/python.py +373 -0
- superqode/superqe/frameworks/registry.py +92 -0
- superqode/superqe/mcp_tools/__init__.py +47 -0
- superqode/superqe/mcp_tools/core_tools.py +418 -0
- superqode/superqe/mcp_tools/registry.py +230 -0
- superqode/superqe/mcp_tools/testing_tools.py +167 -0
- superqode/superqe/noise.py +89 -0
- superqode/superqe/orchestrator.py +778 -0
- superqode/superqe/roles.py +609 -0
- superqode/superqe/session.py +713 -0
- superqode/superqe/skills/__init__.py +57 -0
- superqode/superqe/skills/base.py +106 -0
- superqode/superqe/skills/core_skills.py +899 -0
- superqode/superqe/skills/registry.py +90 -0
- superqode/superqe/verifier.py +101 -0
- superqode/superqe_cli.py +76 -0
- superqode/tool_call.py +358 -0
- superqode/tools/__init__.py +93 -0
- superqode/tools/agent_tools.py +496 -0
- superqode/tools/base.py +324 -0
- superqode/tools/batch_tool.py +133 -0
- superqode/tools/diagnostics.py +311 -0
- superqode/tools/edit_tools.py +653 -0
- superqode/tools/enhanced_base.py +515 -0
- superqode/tools/file_tools.py +269 -0
- superqode/tools/file_tracking.py +45 -0
- superqode/tools/lsp_tools.py +610 -0
- superqode/tools/network_tools.py +350 -0
- superqode/tools/permissions.py +400 -0
- superqode/tools/question_tool.py +324 -0
- superqode/tools/search_tools.py +598 -0
- superqode/tools/shell_tools.py +259 -0
- superqode/tools/todo_tools.py +121 -0
- superqode/tools/validation.py +80 -0
- superqode/tools/web_tools.py +639 -0
- superqode/tui.py +1152 -0
- superqode/tui_integration.py +875 -0
- superqode/tui_widgets/__init__.py +27 -0
- superqode/tui_widgets/widgets/__init__.py +18 -0
- superqode/tui_widgets/widgets/progress.py +185 -0
- superqode/tui_widgets/widgets/tool_display.py +188 -0
- superqode/undo_manager.py +574 -0
- superqode/utils/__init__.py +5 -0
- superqode/utils/error_handling.py +323 -0
- superqode/utils/fuzzy.py +257 -0
- superqode/widgets/__init__.py +477 -0
- superqode/widgets/agent_collab.py +390 -0
- superqode/widgets/agent_store.py +936 -0
- superqode/widgets/agent_switcher.py +395 -0
- superqode/widgets/animation_manager.py +284 -0
- superqode/widgets/code_context.py +356 -0
- superqode/widgets/command_palette.py +412 -0
- superqode/widgets/connection_status.py +537 -0
- superqode/widgets/conversation_history.py +470 -0
- superqode/widgets/diff_indicator.py +155 -0
- superqode/widgets/enhanced_status_bar.py +385 -0
- superqode/widgets/enhanced_toast.py +476 -0
- superqode/widgets/file_browser.py +809 -0
- superqode/widgets/file_reference.py +585 -0
- superqode/widgets/issue_timeline.py +340 -0
- superqode/widgets/leader_key.py +264 -0
- superqode/widgets/mode_switcher.py +445 -0
- superqode/widgets/model_picker.py +234 -0
- superqode/widgets/permission_preview.py +1205 -0
- superqode/widgets/prompt.py +358 -0
- superqode/widgets/provider_connect.py +725 -0
- superqode/widgets/pty_shell.py +587 -0
- superqode/widgets/qe_dashboard.py +321 -0
- superqode/widgets/resizable_sidebar.py +377 -0
- superqode/widgets/response_changes.py +218 -0
- superqode/widgets/response_display.py +528 -0
- superqode/widgets/rich_tool_display.py +613 -0
- superqode/widgets/sidebar_panels.py +1180 -0
- superqode/widgets/slash_complete.py +356 -0
- superqode/widgets/split_view.py +612 -0
- superqode/widgets/status_bar.py +273 -0
- superqode/widgets/superqode_display.py +786 -0
- superqode/widgets/thinking_display.py +815 -0
- superqode/widgets/throbber.py +87 -0
- superqode/widgets/toast.py +206 -0
- superqode/widgets/unified_output.py +1073 -0
- superqode/workspace/__init__.py +75 -0
- superqode/workspace/artifacts.py +472 -0
- superqode/workspace/coordinator.py +353 -0
- superqode/workspace/diff_tracker.py +429 -0
- superqode/workspace/git_guard.py +373 -0
- superqode/workspace/git_snapshot.py +526 -0
- superqode/workspace/manager.py +750 -0
- superqode/workspace/snapshot.py +357 -0
- superqode/workspace/watcher.py +535 -0
- superqode/workspace/worktree.py +440 -0
- superqode-0.1.5.dist-info/METADATA +204 -0
- superqode-0.1.5.dist-info/RECORD +288 -0
- superqode-0.1.5.dist-info/WHEEL +5 -0
- superqode-0.1.5.dist-info/entry_points.txt +3 -0
- superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
- 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
|
+
}
|