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,1018 @@
|
|
|
1
|
+
"""
|
|
2
|
+
QR Generator - Quality Report Generator.
|
|
3
|
+
|
|
4
|
+
Produces research-grade QA reports that transform findings
|
|
5
|
+
from "bug reports" into "evidence-backed decisions."
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, Optional
|
|
15
|
+
import json
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class QRVerdict(Enum):
|
|
19
|
+
"""Overall QIR verdict."""
|
|
20
|
+
|
|
21
|
+
PASS = "pass" # No significant issues
|
|
22
|
+
CONDITIONAL_PASS = "conditional" # Warnings but acceptable
|
|
23
|
+
FAIL = "fail" # Critical issues found
|
|
24
|
+
BLOCKED = "blocked" # Could not complete analysis
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class QRSection(Enum):
|
|
28
|
+
"""Sections of a QIR."""
|
|
29
|
+
|
|
30
|
+
EXECUTIVE_SUMMARY = "executive_summary"
|
|
31
|
+
SCOPE = "scope"
|
|
32
|
+
METHODOLOGY = "methodology"
|
|
33
|
+
FINDINGS = "findings"
|
|
34
|
+
ROOT_CAUSE = "root_cause"
|
|
35
|
+
SUGGESTED_FIXES = "suggested_fixes"
|
|
36
|
+
GENERATED_TESTS = "generated_tests"
|
|
37
|
+
BENCHMARKS = "benchmarks"
|
|
38
|
+
RECOMMENDATIONS = "recommendations"
|
|
39
|
+
APPENDIX = "appendix"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class FindingPriority(Enum):
|
|
43
|
+
"""
|
|
44
|
+
Priority levels for findings.
|
|
45
|
+
|
|
46
|
+
P0 - Drop everything. Blocking release/operations.
|
|
47
|
+
P1 - Urgent. Should be addressed in next cycle.
|
|
48
|
+
P2 - Normal. To be fixed eventually.
|
|
49
|
+
P3 - Low. Nice to have.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
P0 = 0 # Drop everything
|
|
53
|
+
P1 = 1 # Urgent
|
|
54
|
+
P2 = 2 # Normal
|
|
55
|
+
P3 = 3 # Low/Nice to have
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class Finding:
|
|
60
|
+
"""
|
|
61
|
+
A single finding in the QIR.
|
|
62
|
+
|
|
63
|
+
Enhanced with priorities and confidence scores for CI filtering.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
id: str
|
|
67
|
+
severity: str # "critical", "high", "medium", "low", "info"
|
|
68
|
+
category: str # "security", "performance", "reliability", "maintainability"
|
|
69
|
+
title: str
|
|
70
|
+
description: str
|
|
71
|
+
|
|
72
|
+
# Priority
|
|
73
|
+
priority: FindingPriority = FindingPriority.P2
|
|
74
|
+
|
|
75
|
+
# Confidence score (0.0-1.0) for filtering noise
|
|
76
|
+
confidence_score: float = 0.8
|
|
77
|
+
|
|
78
|
+
# Location
|
|
79
|
+
file_path: Optional[str] = None
|
|
80
|
+
line_start: Optional[int] = None
|
|
81
|
+
line_end: Optional[int] = None
|
|
82
|
+
|
|
83
|
+
# Evidence and context
|
|
84
|
+
evidence: Optional[str] = None
|
|
85
|
+
evidence_snippet: Optional[str] = None # Code snippet showing the issue
|
|
86
|
+
reproduction_steps: List[str] = field(default_factory=list)
|
|
87
|
+
|
|
88
|
+
# Fix information
|
|
89
|
+
suggested_fix: Optional[str] = None
|
|
90
|
+
suggested_fix_snippet: Optional[str] = None # Code showing the fix
|
|
91
|
+
patch_id: Optional[str] = None
|
|
92
|
+
|
|
93
|
+
# Metadata
|
|
94
|
+
found_by: Optional[str] = None # QE role that found this
|
|
95
|
+
references: List[str] = field(default_factory=list)
|
|
96
|
+
tags: List[str] = field(default_factory=list)
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def severity_icon(self) -> str:
|
|
100
|
+
"""Get emoji icon for severity."""
|
|
101
|
+
icons = {
|
|
102
|
+
"critical": "🔴",
|
|
103
|
+
"high": "🟠",
|
|
104
|
+
"medium": "🟡",
|
|
105
|
+
"low": "🔵",
|
|
106
|
+
"info": "⚪",
|
|
107
|
+
}
|
|
108
|
+
return icons.get(self.severity, "⚪")
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def priority_label(self) -> str:
|
|
112
|
+
"""Get priority label like [P0], [P1], etc."""
|
|
113
|
+
return f"[P{self.priority.value}]"
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def location(self) -> str:
|
|
117
|
+
"""Get formatted location string."""
|
|
118
|
+
if not self.file_path:
|
|
119
|
+
return ""
|
|
120
|
+
loc = self.file_path
|
|
121
|
+
if self.line_start:
|
|
122
|
+
loc += f":{self.line_start}"
|
|
123
|
+
if self.line_end and self.line_end != self.line_start:
|
|
124
|
+
loc += f"-{self.line_end}"
|
|
125
|
+
return loc
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def full_title(self) -> str:
|
|
129
|
+
"""Get title with priority prefix."""
|
|
130
|
+
return f"{self.priority_label} {self.title}"
|
|
131
|
+
|
|
132
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
133
|
+
"""Convert to dictionary for JSON serialization."""
|
|
134
|
+
return {
|
|
135
|
+
"id": self.id,
|
|
136
|
+
"severity": self.severity,
|
|
137
|
+
"priority": self.priority.value,
|
|
138
|
+
"confidence_score": self.confidence_score,
|
|
139
|
+
"category": self.category,
|
|
140
|
+
"title": self.title,
|
|
141
|
+
"description": self.description,
|
|
142
|
+
"file_path": self.file_path,
|
|
143
|
+
"line_range": {
|
|
144
|
+
"start": self.line_start,
|
|
145
|
+
"end": self.line_end,
|
|
146
|
+
}
|
|
147
|
+
if self.line_start
|
|
148
|
+
else None,
|
|
149
|
+
"location": self.location,
|
|
150
|
+
"evidence": self.evidence,
|
|
151
|
+
"suggested_fix": self.suggested_fix,
|
|
152
|
+
"patch_id": self.patch_id,
|
|
153
|
+
"found_by": self.found_by,
|
|
154
|
+
"tags": self.tags,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@dataclass
|
|
159
|
+
class TestArtifact:
|
|
160
|
+
"""A generated test artifact."""
|
|
161
|
+
|
|
162
|
+
id: str
|
|
163
|
+
test_type: str # "unit", "integration", "api", "fuzz", etc.
|
|
164
|
+
filename: str
|
|
165
|
+
description: str
|
|
166
|
+
target_file: Optional[str] = None
|
|
167
|
+
coverage_added: Optional[float] = None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@dataclass
|
|
171
|
+
class PatchArtifact:
|
|
172
|
+
"""A suggested fix patch."""
|
|
173
|
+
|
|
174
|
+
id: str
|
|
175
|
+
filename: str
|
|
176
|
+
description: str
|
|
177
|
+
target_file: str
|
|
178
|
+
lines_added: int = 0
|
|
179
|
+
lines_removed: int = 0
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@dataclass
|
|
183
|
+
class BenchmarkResult:
|
|
184
|
+
"""A benchmark or validation result."""
|
|
185
|
+
|
|
186
|
+
name: str
|
|
187
|
+
metric: str
|
|
188
|
+
value: float
|
|
189
|
+
unit: str
|
|
190
|
+
baseline: Optional[float] = None
|
|
191
|
+
threshold: Optional[float] = None
|
|
192
|
+
passed: bool = True
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@dataclass
|
|
196
|
+
class VerifiedFix:
|
|
197
|
+
"""A fix that has been verified through the suggestion workflow.
|
|
198
|
+
|
|
199
|
+
Records the full proof chain:
|
|
200
|
+
1. Original issue found
|
|
201
|
+
2. Fix applied in sandbox
|
|
202
|
+
3. Tests run before/after
|
|
203
|
+
4. Proof of improvement
|
|
204
|
+
5. Code reverted (always)
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
finding_id: str
|
|
208
|
+
finding_title: str
|
|
209
|
+
patch_id: str
|
|
210
|
+
patch_file: str
|
|
211
|
+
|
|
212
|
+
# Verification status
|
|
213
|
+
fix_verified: bool = False
|
|
214
|
+
is_improvement: bool = False
|
|
215
|
+
|
|
216
|
+
# Test metrics
|
|
217
|
+
tests_before_passed: int = 0
|
|
218
|
+
tests_before_total: int = 0
|
|
219
|
+
tests_after_passed: int = 0
|
|
220
|
+
tests_after_total: int = 0
|
|
221
|
+
|
|
222
|
+
# Coverage
|
|
223
|
+
coverage_before: Optional[float] = None
|
|
224
|
+
coverage_after: Optional[float] = None
|
|
225
|
+
|
|
226
|
+
# Evidence
|
|
227
|
+
verification_evidence: List[str] = field(default_factory=list)
|
|
228
|
+
verification_duration_ms: int = 0
|
|
229
|
+
|
|
230
|
+
# Confidence in the fix
|
|
231
|
+
fix_confidence: float = 0.8
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def tests_fixed(self) -> int:
|
|
235
|
+
"""Number of tests that now pass after the fix."""
|
|
236
|
+
return max(0, self.tests_after_passed - self.tests_before_passed)
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def tests_broken(self) -> int:
|
|
240
|
+
"""Number of tests broken by the fix (regressions)."""
|
|
241
|
+
after_failed = self.tests_after_total - self.tests_after_passed
|
|
242
|
+
before_failed = self.tests_before_total - self.tests_before_passed
|
|
243
|
+
return max(0, after_failed - before_failed)
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def coverage_delta(self) -> Optional[float]:
|
|
247
|
+
"""Change in coverage."""
|
|
248
|
+
if self.coverage_before is not None and self.coverage_after is not None:
|
|
249
|
+
return self.coverage_after - self.coverage_before
|
|
250
|
+
return None
|
|
251
|
+
|
|
252
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
253
|
+
return {
|
|
254
|
+
"finding_id": self.finding_id,
|
|
255
|
+
"finding_title": self.finding_title,
|
|
256
|
+
"patch_id": self.patch_id,
|
|
257
|
+
"patch_file": self.patch_file,
|
|
258
|
+
"fix_verified": self.fix_verified,
|
|
259
|
+
"is_improvement": self.is_improvement,
|
|
260
|
+
"tests_fixed": self.tests_fixed,
|
|
261
|
+
"tests_broken": self.tests_broken,
|
|
262
|
+
"metrics": {
|
|
263
|
+
"before": {
|
|
264
|
+
"tests_passed": self.tests_before_passed,
|
|
265
|
+
"tests_total": self.tests_before_total,
|
|
266
|
+
"coverage": self.coverage_before,
|
|
267
|
+
},
|
|
268
|
+
"after": {
|
|
269
|
+
"tests_passed": self.tests_after_passed,
|
|
270
|
+
"tests_total": self.tests_after_total,
|
|
271
|
+
"coverage": self.coverage_after,
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
"coverage_delta": self.coverage_delta,
|
|
275
|
+
"fix_confidence": self.fix_confidence,
|
|
276
|
+
"verification_evidence": self.verification_evidence,
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@dataclass
|
|
281
|
+
class QRData:
|
|
282
|
+
"""All data for generating a QIR."""
|
|
283
|
+
|
|
284
|
+
session_id: str
|
|
285
|
+
mode: str
|
|
286
|
+
started_at: datetime
|
|
287
|
+
ended_at: Optional[datetime] = None
|
|
288
|
+
|
|
289
|
+
# Scope
|
|
290
|
+
target_description: str = ""
|
|
291
|
+
files_analyzed: List[str] = field(default_factory=list)
|
|
292
|
+
total_lines: int = 0
|
|
293
|
+
|
|
294
|
+
# Methodology
|
|
295
|
+
roles_used: List[str] = field(default_factory=list)
|
|
296
|
+
tools_used: List[str] = field(default_factory=list)
|
|
297
|
+
methodology_notes: List[str] = field(default_factory=list)
|
|
298
|
+
|
|
299
|
+
# Findings
|
|
300
|
+
findings: List[Finding] = field(default_factory=list)
|
|
301
|
+
|
|
302
|
+
# Artifacts
|
|
303
|
+
generated_tests: List[TestArtifact] = field(default_factory=list)
|
|
304
|
+
patches: List[PatchArtifact] = field(default_factory=list)
|
|
305
|
+
|
|
306
|
+
# Benchmarks
|
|
307
|
+
benchmarks: List[BenchmarkResult] = field(default_factory=list)
|
|
308
|
+
coverage_before: Optional[float] = None
|
|
309
|
+
coverage_after: Optional[float] = None
|
|
310
|
+
|
|
311
|
+
# Verified fixes (when allow_suggestions is enabled)
|
|
312
|
+
verified_fixes: List[VerifiedFix] = field(default_factory=list)
|
|
313
|
+
allow_suggestions_enabled: bool = False
|
|
314
|
+
|
|
315
|
+
# Meta
|
|
316
|
+
blocked_operations: List[str] = field(default_factory=list)
|
|
317
|
+
errors: List[str] = field(default_factory=list)
|
|
318
|
+
|
|
319
|
+
@property
|
|
320
|
+
def duration_seconds(self) -> float:
|
|
321
|
+
"""Get session duration."""
|
|
322
|
+
if self.ended_at:
|
|
323
|
+
return (self.ended_at - self.started_at).total_seconds()
|
|
324
|
+
return (datetime.now() - self.started_at).total_seconds()
|
|
325
|
+
|
|
326
|
+
@property
|
|
327
|
+
def critical_count(self) -> int:
|
|
328
|
+
"""Count of critical findings."""
|
|
329
|
+
return sum(1 for f in self.findings if f.severity == "critical")
|
|
330
|
+
|
|
331
|
+
@property
|
|
332
|
+
def high_count(self) -> int:
|
|
333
|
+
"""Count of high severity findings."""
|
|
334
|
+
return sum(1 for f in self.findings if f.severity == "high")
|
|
335
|
+
|
|
336
|
+
@property
|
|
337
|
+
def verdict(self) -> QRVerdict:
|
|
338
|
+
"""Determine overall verdict."""
|
|
339
|
+
if self.errors:
|
|
340
|
+
return QRVerdict.BLOCKED
|
|
341
|
+
if self.critical_count > 0:
|
|
342
|
+
return QRVerdict.FAIL
|
|
343
|
+
if self.high_count > 0 or sum(1 for f in self.findings if f.severity == "medium") > 3:
|
|
344
|
+
return QRVerdict.CONDITIONAL_PASS
|
|
345
|
+
return QRVerdict.PASS
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
class QRGenerator:
|
|
349
|
+
"""
|
|
350
|
+
Generates Quality Investigation Reports.
|
|
351
|
+
|
|
352
|
+
Produces Markdown reports with optional JSON output for CI integration.
|
|
353
|
+
"""
|
|
354
|
+
|
|
355
|
+
def __init__(self, data: QRData):
|
|
356
|
+
self.data = data
|
|
357
|
+
|
|
358
|
+
def generate_markdown(self) -> str:
|
|
359
|
+
"""Generate the full QIR in Markdown format."""
|
|
360
|
+
sections = [
|
|
361
|
+
self._header(),
|
|
362
|
+
self._executive_summary(),
|
|
363
|
+
self._scope(),
|
|
364
|
+
self._methodology(),
|
|
365
|
+
self._findings(),
|
|
366
|
+
self._suggested_fixes(),
|
|
367
|
+
self._fix_verification(), # New: verification results
|
|
368
|
+
self._generated_tests(),
|
|
369
|
+
self._benchmarks(),
|
|
370
|
+
self._recommendations(),
|
|
371
|
+
self._appendix(),
|
|
372
|
+
self._footer(),
|
|
373
|
+
]
|
|
374
|
+
|
|
375
|
+
return "\n".join(filter(None, sections))
|
|
376
|
+
|
|
377
|
+
def generate_json(self) -> Dict[str, Any]:
|
|
378
|
+
"""Generate QIR data as JSON for CI integration."""
|
|
379
|
+
# Calculate confidence statistics
|
|
380
|
+
confidence_scores = [f.confidence_score for f in self.data.findings]
|
|
381
|
+
avg_confidence = (
|
|
382
|
+
sum(confidence_scores) / len(confidence_scores) if confidence_scores else 1.0
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
# Priority breakdown
|
|
386
|
+
priority_counts = {f"P{i}": 0 for i in range(4)}
|
|
387
|
+
for f in self.data.findings:
|
|
388
|
+
priority_counts[f"P{f.priority.value}"] += 1
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
"version": "1.0",
|
|
392
|
+
"schema": "superqode-qr-v1",
|
|
393
|
+
"session_id": self.data.session_id,
|
|
394
|
+
"mode": self.data.mode,
|
|
395
|
+
"started_at": self.data.started_at.isoformat(),
|
|
396
|
+
"ended_at": self.data.ended_at.isoformat() if self.data.ended_at else None,
|
|
397
|
+
"duration_seconds": self.data.duration_seconds,
|
|
398
|
+
# Verdict with confidence
|
|
399
|
+
"verdict": self.data.verdict.value,
|
|
400
|
+
"overall_correctness": "correct"
|
|
401
|
+
if self.data.verdict == QRVerdict.PASS
|
|
402
|
+
else "incorrect",
|
|
403
|
+
"overall_confidence_score": avg_confidence,
|
|
404
|
+
"overall_explanation": self._generate_verdict_explanation(),
|
|
405
|
+
# Summary statistics
|
|
406
|
+
"summary": {
|
|
407
|
+
"total_findings": len(self.data.findings),
|
|
408
|
+
"by_severity": {
|
|
409
|
+
"critical": self.data.critical_count,
|
|
410
|
+
"high": self.data.high_count,
|
|
411
|
+
"medium": sum(1 for f in self.data.findings if f.severity == "medium"),
|
|
412
|
+
"low": sum(1 for f in self.data.findings if f.severity == "low"),
|
|
413
|
+
"info": sum(1 for f in self.data.findings if f.severity == "info"),
|
|
414
|
+
},
|
|
415
|
+
"by_priority": priority_counts,
|
|
416
|
+
"tests_generated": len(self.data.generated_tests),
|
|
417
|
+
"patches_generated": len(self.data.patches),
|
|
418
|
+
},
|
|
419
|
+
# Detailed findings (CI-friendly format)
|
|
420
|
+
"findings": [f.to_dict() for f in self.data.findings],
|
|
421
|
+
# Coverage information
|
|
422
|
+
"coverage": {
|
|
423
|
+
"before": self.data.coverage_before,
|
|
424
|
+
"after": self.data.coverage_after,
|
|
425
|
+
"change": (self.data.coverage_after - self.data.coverage_before)
|
|
426
|
+
if self.data.coverage_before and self.data.coverage_after
|
|
427
|
+
else None,
|
|
428
|
+
},
|
|
429
|
+
# Generated artifacts
|
|
430
|
+
"artifacts": {
|
|
431
|
+
"tests": [
|
|
432
|
+
{
|
|
433
|
+
"filename": t.filename,
|
|
434
|
+
"type": t.test_type,
|
|
435
|
+
"target": t.target_file,
|
|
436
|
+
}
|
|
437
|
+
for t in self.data.generated_tests
|
|
438
|
+
],
|
|
439
|
+
"patches": [
|
|
440
|
+
{
|
|
441
|
+
"filename": p.filename,
|
|
442
|
+
"target": p.target_file,
|
|
443
|
+
"lines_added": p.lines_added,
|
|
444
|
+
"lines_removed": p.lines_removed,
|
|
445
|
+
}
|
|
446
|
+
for p in self.data.patches
|
|
447
|
+
],
|
|
448
|
+
},
|
|
449
|
+
# Verified fixes (when allow_suggestions enabled)
|
|
450
|
+
"verified_fixes": {
|
|
451
|
+
"enabled": self.data.allow_suggestions_enabled,
|
|
452
|
+
"total": len(self.data.verified_fixes),
|
|
453
|
+
"verified": sum(1 for f in self.data.verified_fixes if f.fix_verified),
|
|
454
|
+
"improvements": sum(1 for f in self.data.verified_fixes if f.is_improvement),
|
|
455
|
+
"fixes": [vf.to_dict() for vf in self.data.verified_fixes],
|
|
456
|
+
}
|
|
457
|
+
if self.data.verified_fixes
|
|
458
|
+
else None,
|
|
459
|
+
# Metadata
|
|
460
|
+
"metadata": {
|
|
461
|
+
"roles_used": self.data.roles_used,
|
|
462
|
+
"files_analyzed": len(self.data.files_analyzed),
|
|
463
|
+
"total_lines": self.data.total_lines,
|
|
464
|
+
"blocked_operations": len(self.data.blocked_operations),
|
|
465
|
+
"errors": len(self.data.errors),
|
|
466
|
+
"allow_suggestions": self.data.allow_suggestions_enabled,
|
|
467
|
+
},
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
def _generate_verdict_explanation(self) -> str:
|
|
471
|
+
"""Generate a brief explanation for the verdict."""
|
|
472
|
+
if self.data.verdict == QRVerdict.PASS:
|
|
473
|
+
return "No significant issues were found during the investigation."
|
|
474
|
+
elif self.data.verdict == QRVerdict.CONDITIONAL_PASS:
|
|
475
|
+
return f"Found {self.data.high_count} high-severity issues that should be reviewed."
|
|
476
|
+
elif self.data.verdict == QRVerdict.FAIL:
|
|
477
|
+
return f"Found {self.data.critical_count} critical issues that require immediate attention."
|
|
478
|
+
else:
|
|
479
|
+
return f"Analysis could not complete due to {len(self.data.errors)} errors."
|
|
480
|
+
|
|
481
|
+
def _header(self) -> str:
|
|
482
|
+
"""Generate report header."""
|
|
483
|
+
return f"""# Quality Report (QR)
|
|
484
|
+
|
|
485
|
+
**Session ID**: `{self.data.session_id}`
|
|
486
|
+
**Mode**: {self.data.mode}
|
|
487
|
+
**Date**: {self.data.started_at.strftime("%Y-%m-%d %H:%M")}
|
|
488
|
+
**Duration**: {self.data.duration_seconds:.1f}s
|
|
489
|
+
|
|
490
|
+
---
|
|
491
|
+
"""
|
|
492
|
+
|
|
493
|
+
def _executive_summary(self) -> str:
|
|
494
|
+
"""Generate executive summary section."""
|
|
495
|
+
verdict = self.data.verdict
|
|
496
|
+
|
|
497
|
+
verdict_display = {
|
|
498
|
+
QRVerdict.PASS: "🟢 **PASS** - No significant issues found",
|
|
499
|
+
QRVerdict.CONDITIONAL_PASS: "🟡 **CONDITIONAL PASS** - Issues found, review recommended",
|
|
500
|
+
QRVerdict.FAIL: "🔴 **FAIL** - Critical issues require attention",
|
|
501
|
+
QRVerdict.BLOCKED: "⚫ **BLOCKED** - Analysis could not complete",
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
lines = [
|
|
505
|
+
"## Executive Summary",
|
|
506
|
+
"",
|
|
507
|
+
f"**Verdict**: {verdict_display[verdict]}",
|
|
508
|
+
"",
|
|
509
|
+
"### Findings Overview",
|
|
510
|
+
"",
|
|
511
|
+
"| Severity | Count | Action |",
|
|
512
|
+
"|----------|-------|--------|",
|
|
513
|
+
f"| 🔴 Critical | {self.data.critical_count} | Must fix |",
|
|
514
|
+
f"| 🟠 High | {self.data.high_count} | Should fix |",
|
|
515
|
+
f"| 🟡 Medium | {sum(1 for f in self.data.findings if f.severity == 'medium')} | Consider |",
|
|
516
|
+
f"| 🔵 Low | {sum(1 for f in self.data.findings if f.severity == 'low')} | Optional |",
|
|
517
|
+
f"| ⚪ Info | {sum(1 for f in self.data.findings if f.severity == 'info')} | Note |",
|
|
518
|
+
"",
|
|
519
|
+
]
|
|
520
|
+
|
|
521
|
+
# Coverage change
|
|
522
|
+
if self.data.coverage_before is not None and self.data.coverage_after is not None:
|
|
523
|
+
change = self.data.coverage_after - self.data.coverage_before
|
|
524
|
+
change_str = f"+{change:.1f}%" if change >= 0 else f"{change:.1f}%"
|
|
525
|
+
lines.extend(
|
|
526
|
+
[
|
|
527
|
+
"### Coverage Impact",
|
|
528
|
+
"",
|
|
529
|
+
f"- Before: {self.data.coverage_before:.1f}%",
|
|
530
|
+
f"- After: {self.data.coverage_after:.1f}%",
|
|
531
|
+
f"- Change: {change_str}",
|
|
532
|
+
"",
|
|
533
|
+
]
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
# Artifacts generated
|
|
537
|
+
if self.data.generated_tests or self.data.patches:
|
|
538
|
+
lines.extend(
|
|
539
|
+
[
|
|
540
|
+
"### Generated Artifacts",
|
|
541
|
+
"",
|
|
542
|
+
f"- 📝 {len(self.data.patches)} suggested fixes",
|
|
543
|
+
f"- 🧪 {len(self.data.generated_tests)} new tests",
|
|
544
|
+
"",
|
|
545
|
+
]
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
return "\n".join(lines)
|
|
549
|
+
|
|
550
|
+
def _scope(self) -> str:
|
|
551
|
+
"""Generate scope section."""
|
|
552
|
+
lines = [
|
|
553
|
+
"## Investigation Scope",
|
|
554
|
+
"",
|
|
555
|
+
]
|
|
556
|
+
|
|
557
|
+
if self.data.target_description:
|
|
558
|
+
lines.extend(
|
|
559
|
+
[
|
|
560
|
+
f"**Target**: {self.data.target_description}",
|
|
561
|
+
"",
|
|
562
|
+
]
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
lines.extend(
|
|
566
|
+
[
|
|
567
|
+
f"- Files analyzed: {len(self.data.files_analyzed)}",
|
|
568
|
+
f"- Total lines: {self.data.total_lines:,}",
|
|
569
|
+
]
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
if self.data.files_analyzed:
|
|
573
|
+
lines.extend(
|
|
574
|
+
[
|
|
575
|
+
"",
|
|
576
|
+
"<details>",
|
|
577
|
+
"<summary>Files analyzed</summary>",
|
|
578
|
+
"",
|
|
579
|
+
]
|
|
580
|
+
)
|
|
581
|
+
for f in self.data.files_analyzed[:20]: # Limit to 20
|
|
582
|
+
lines.append(f"- `{f}`")
|
|
583
|
+
if len(self.data.files_analyzed) > 20:
|
|
584
|
+
lines.append(f"- ... and {len(self.data.files_analyzed) - 20} more")
|
|
585
|
+
lines.extend(
|
|
586
|
+
[
|
|
587
|
+
"",
|
|
588
|
+
"</details>",
|
|
589
|
+
]
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
lines.append("")
|
|
593
|
+
return "\n".join(lines)
|
|
594
|
+
|
|
595
|
+
def _methodology(self) -> str:
|
|
596
|
+
"""Generate methodology section."""
|
|
597
|
+
lines = [
|
|
598
|
+
"## Methodology",
|
|
599
|
+
"",
|
|
600
|
+
]
|
|
601
|
+
|
|
602
|
+
if self.data.roles_used:
|
|
603
|
+
lines.append("**QE Roles Used**:")
|
|
604
|
+
for role in self.data.roles_used:
|
|
605
|
+
lines.append(f"- {role}")
|
|
606
|
+
lines.append("")
|
|
607
|
+
|
|
608
|
+
if self.data.tools_used:
|
|
609
|
+
lines.append("**Tools/Techniques**:")
|
|
610
|
+
for tool in self.data.tools_used:
|
|
611
|
+
lines.append(f"- {tool}")
|
|
612
|
+
lines.append("")
|
|
613
|
+
|
|
614
|
+
if self.data.methodology_notes:
|
|
615
|
+
lines.append("**Approach**:")
|
|
616
|
+
for note in self.data.methodology_notes:
|
|
617
|
+
lines.append(f"- {note}")
|
|
618
|
+
lines.append("")
|
|
619
|
+
|
|
620
|
+
return "\n".join(lines)
|
|
621
|
+
|
|
622
|
+
def _findings(self) -> str:
|
|
623
|
+
"""Generate findings section."""
|
|
624
|
+
if not self.data.findings:
|
|
625
|
+
return """## Findings
|
|
626
|
+
|
|
627
|
+
✅ No issues found during this investigation.
|
|
628
|
+
"""
|
|
629
|
+
|
|
630
|
+
lines = [
|
|
631
|
+
"## Findings",
|
|
632
|
+
"",
|
|
633
|
+
]
|
|
634
|
+
|
|
635
|
+
# Group by severity
|
|
636
|
+
for severity in ["critical", "high", "medium", "low", "info"]:
|
|
637
|
+
severity_findings = [f for f in self.data.findings if f.severity == severity]
|
|
638
|
+
if not severity_findings:
|
|
639
|
+
continue
|
|
640
|
+
|
|
641
|
+
severity_title = severity.title()
|
|
642
|
+
lines.append(f"### {severity_title} ({len(severity_findings)})")
|
|
643
|
+
lines.append("")
|
|
644
|
+
|
|
645
|
+
for finding in severity_findings:
|
|
646
|
+
lines.extend(self._render_finding(finding))
|
|
647
|
+
lines.append("")
|
|
648
|
+
|
|
649
|
+
return "\n".join(lines)
|
|
650
|
+
|
|
651
|
+
def _render_finding(self, finding: Finding) -> List[str]:
|
|
652
|
+
"""Render a single finding."""
|
|
653
|
+
lines = [
|
|
654
|
+
f"#### {finding.severity_icon} {finding.title}",
|
|
655
|
+
"",
|
|
656
|
+
]
|
|
657
|
+
|
|
658
|
+
if finding.location:
|
|
659
|
+
lines.append(f"**Location**: `{finding.location}`")
|
|
660
|
+
lines.append("")
|
|
661
|
+
|
|
662
|
+
lines.append(finding.description)
|
|
663
|
+
lines.append("")
|
|
664
|
+
|
|
665
|
+
if finding.evidence:
|
|
666
|
+
lines.extend(
|
|
667
|
+
[
|
|
668
|
+
"**Evidence**:",
|
|
669
|
+
"```",
|
|
670
|
+
finding.evidence,
|
|
671
|
+
"```",
|
|
672
|
+
"",
|
|
673
|
+
]
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
if finding.reproduction_steps:
|
|
677
|
+
lines.append("**Reproduction Steps**:")
|
|
678
|
+
for i, step in enumerate(finding.reproduction_steps, 1):
|
|
679
|
+
lines.append(f"{i}. {step}")
|
|
680
|
+
lines.append("")
|
|
681
|
+
|
|
682
|
+
if finding.patch_id:
|
|
683
|
+
lines.append(f"💡 **Suggested Fix**: See patch `{finding.patch_id}`")
|
|
684
|
+
lines.append("")
|
|
685
|
+
|
|
686
|
+
if finding.references:
|
|
687
|
+
lines.append("**References**:")
|
|
688
|
+
for ref in finding.references:
|
|
689
|
+
lines.append(f"- {ref}")
|
|
690
|
+
lines.append("")
|
|
691
|
+
|
|
692
|
+
return lines
|
|
693
|
+
|
|
694
|
+
def _suggested_fixes(self) -> str:
|
|
695
|
+
"""Generate suggested fixes section."""
|
|
696
|
+
if not self.data.patches:
|
|
697
|
+
return ""
|
|
698
|
+
|
|
699
|
+
lines = [
|
|
700
|
+
"## Suggested Fixes",
|
|
701
|
+
"",
|
|
702
|
+
"The following patches have been generated and are available in `.superqode/qe-artifacts/patches/`:",
|
|
703
|
+
"",
|
|
704
|
+
"| Patch | Target | Changes | Description |",
|
|
705
|
+
"|-------|--------|---------|-------------|",
|
|
706
|
+
]
|
|
707
|
+
|
|
708
|
+
for patch in self.data.patches:
|
|
709
|
+
changes = f"+{patch.lines_added}/-{patch.lines_removed}"
|
|
710
|
+
lines.append(
|
|
711
|
+
f"| `{patch.filename}` | `{patch.target_file}` | {changes} | {patch.description} |"
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
lines.extend(
|
|
715
|
+
[
|
|
716
|
+
"",
|
|
717
|
+
"**To apply a patch**:",
|
|
718
|
+
"```bash",
|
|
719
|
+
"cd /path/to/project",
|
|
720
|
+
"patch -p1 < .superqode/qe-artifacts/patches/<patch-file>.patch",
|
|
721
|
+
"```",
|
|
722
|
+
"",
|
|
723
|
+
]
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
return "\n".join(lines)
|
|
727
|
+
|
|
728
|
+
def _fix_verification(self) -> str:
|
|
729
|
+
"""Generate fix verification section (when allow_suggestions is enabled)."""
|
|
730
|
+
if not self.data.verified_fixes:
|
|
731
|
+
return ""
|
|
732
|
+
|
|
733
|
+
lines = [
|
|
734
|
+
"## Fix Verification Results",
|
|
735
|
+
"",
|
|
736
|
+
"The following fixes were verified in sandbox environment.",
|
|
737
|
+
"All changes have been **reverted** - patches preserved for your review.",
|
|
738
|
+
"",
|
|
739
|
+
]
|
|
740
|
+
|
|
741
|
+
# Summary table
|
|
742
|
+
verified_count = sum(1 for f in self.data.verified_fixes if f.fix_verified)
|
|
743
|
+
improvement_count = sum(1 for f in self.data.verified_fixes if f.is_improvement)
|
|
744
|
+
|
|
745
|
+
lines.extend(
|
|
746
|
+
[
|
|
747
|
+
"### Summary",
|
|
748
|
+
"",
|
|
749
|
+
f"- Total fixes attempted: {len(self.data.verified_fixes)}",
|
|
750
|
+
f"- Verified successful: {verified_count}",
|
|
751
|
+
f"- Confirmed improvements: {improvement_count}",
|
|
752
|
+
"",
|
|
753
|
+
"### Verification Details",
|
|
754
|
+
"",
|
|
755
|
+
"| Finding | Fix Status | Tests Before | Tests After | Improvement |",
|
|
756
|
+
"|---------|------------|--------------|-------------|-------------|",
|
|
757
|
+
]
|
|
758
|
+
)
|
|
759
|
+
|
|
760
|
+
for vf in self.data.verified_fixes:
|
|
761
|
+
status = "✅ Verified" if vf.fix_verified else "❌ Failed"
|
|
762
|
+
before = f"{vf.tests_before_passed}/{vf.tests_before_total}"
|
|
763
|
+
after = f"{vf.tests_after_passed}/{vf.tests_after_total}"
|
|
764
|
+
improvement = "✅ Yes" if vf.is_improvement else "❌ No"
|
|
765
|
+
|
|
766
|
+
lines.append(
|
|
767
|
+
f"| {vf.finding_title[:30]}... | {status} | {before} | {after} | {improvement} |"
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
lines.append("")
|
|
771
|
+
|
|
772
|
+
# Detailed verification for improvements
|
|
773
|
+
improvements = [vf for vf in self.data.verified_fixes if vf.is_improvement]
|
|
774
|
+
if improvements:
|
|
775
|
+
lines.extend(
|
|
776
|
+
[
|
|
777
|
+
"### Recommended Fixes (Verified Improvements)",
|
|
778
|
+
"",
|
|
779
|
+
]
|
|
780
|
+
)
|
|
781
|
+
|
|
782
|
+
for vf in improvements:
|
|
783
|
+
lines.extend(
|
|
784
|
+
[
|
|
785
|
+
f"#### {vf.finding_title}",
|
|
786
|
+
"",
|
|
787
|
+
f"**Patch**: `{vf.patch_file}`",
|
|
788
|
+
f"**Confidence**: {vf.fix_confidence:.0%}",
|
|
789
|
+
"",
|
|
790
|
+
"**Verification Evidence**:",
|
|
791
|
+
"",
|
|
792
|
+
]
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
for evidence in vf.verification_evidence:
|
|
796
|
+
lines.append(f"- {evidence}")
|
|
797
|
+
|
|
798
|
+
lines.append("")
|
|
799
|
+
|
|
800
|
+
# Before/after comparison
|
|
801
|
+
if vf.tests_fixed > 0:
|
|
802
|
+
lines.append(f"✅ **Fixed {vf.tests_fixed} failing test(s)**")
|
|
803
|
+
|
|
804
|
+
if vf.coverage_delta and vf.coverage_delta > 0:
|
|
805
|
+
lines.append(f"✅ **Coverage improved by {vf.coverage_delta:.1f}%**")
|
|
806
|
+
|
|
807
|
+
lines.extend(
|
|
808
|
+
[
|
|
809
|
+
"",
|
|
810
|
+
"---",
|
|
811
|
+
"",
|
|
812
|
+
]
|
|
813
|
+
)
|
|
814
|
+
|
|
815
|
+
# Note about revert
|
|
816
|
+
lines.extend(
|
|
817
|
+
[
|
|
818
|
+
"### Important",
|
|
819
|
+
"",
|
|
820
|
+
"🔄 **All changes have been reverted to preserve your original code.**",
|
|
821
|
+
"",
|
|
822
|
+
"The patches are available in `.superqode/qe-artifacts/patches/` for you to review and apply.",
|
|
823
|
+
"",
|
|
824
|
+
]
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
return "\n".join(lines)
|
|
828
|
+
|
|
829
|
+
def _generated_tests(self) -> str:
|
|
830
|
+
"""Generate tests section."""
|
|
831
|
+
if not self.data.generated_tests:
|
|
832
|
+
return ""
|
|
833
|
+
|
|
834
|
+
lines = [
|
|
835
|
+
"## Generated Tests",
|
|
836
|
+
"",
|
|
837
|
+
"The following tests have been generated and are available in `.superqode/qe-artifacts/generated-tests/`:",
|
|
838
|
+
"",
|
|
839
|
+
]
|
|
840
|
+
|
|
841
|
+
# Group by type
|
|
842
|
+
by_type: Dict[str, List[TestArtifact]] = {}
|
|
843
|
+
for test in self.data.generated_tests:
|
|
844
|
+
by_type.setdefault(test.test_type, []).append(test)
|
|
845
|
+
|
|
846
|
+
for test_type, tests in by_type.items():
|
|
847
|
+
lines.append(f"### {test_type.title()} Tests ({len(tests)})")
|
|
848
|
+
lines.append("")
|
|
849
|
+
|
|
850
|
+
for test in tests:
|
|
851
|
+
target = f" (for `{test.target_file}`)" if test.target_file else ""
|
|
852
|
+
lines.append(f"- `{test.filename}`{target}: {test.description}")
|
|
853
|
+
|
|
854
|
+
lines.append("")
|
|
855
|
+
|
|
856
|
+
lines.extend(
|
|
857
|
+
[
|
|
858
|
+
"**To run generated tests**:",
|
|
859
|
+
"```bash",
|
|
860
|
+
"# Copy tests to your test directory",
|
|
861
|
+
"cp .superqode/qe-artifacts/generated-tests/unit/* tests/",
|
|
862
|
+
"",
|
|
863
|
+
"# Run tests",
|
|
864
|
+
"pytest tests/",
|
|
865
|
+
"```",
|
|
866
|
+
"",
|
|
867
|
+
]
|
|
868
|
+
)
|
|
869
|
+
|
|
870
|
+
return "\n".join(lines)
|
|
871
|
+
|
|
872
|
+
def _benchmarks(self) -> str:
|
|
873
|
+
"""Generate benchmarks section."""
|
|
874
|
+
if not self.data.benchmarks:
|
|
875
|
+
return ""
|
|
876
|
+
|
|
877
|
+
lines = [
|
|
878
|
+
"## Benchmark Results",
|
|
879
|
+
"",
|
|
880
|
+
"| Metric | Value | Baseline | Status |",
|
|
881
|
+
"|--------|-------|----------|--------|",
|
|
882
|
+
]
|
|
883
|
+
|
|
884
|
+
for bench in self.data.benchmarks:
|
|
885
|
+
status = "✅" if bench.passed else "❌"
|
|
886
|
+
baseline = f"{bench.baseline}{bench.unit}" if bench.baseline else "-"
|
|
887
|
+
lines.append(f"| {bench.name} | {bench.value}{bench.unit} | {baseline} | {status} |")
|
|
888
|
+
|
|
889
|
+
lines.append("")
|
|
890
|
+
return "\n".join(lines)
|
|
891
|
+
|
|
892
|
+
def _recommendations(self) -> str:
|
|
893
|
+
"""Generate recommendations section."""
|
|
894
|
+
lines = [
|
|
895
|
+
"## Recommendations",
|
|
896
|
+
"",
|
|
897
|
+
]
|
|
898
|
+
|
|
899
|
+
# Priority actions based on findings
|
|
900
|
+
if self.data.critical_count > 0:
|
|
901
|
+
lines.extend(
|
|
902
|
+
[
|
|
903
|
+
"### 🚨 Immediate Actions Required",
|
|
904
|
+
"",
|
|
905
|
+
]
|
|
906
|
+
)
|
|
907
|
+
for f in self.data.findings:
|
|
908
|
+
if f.severity == "critical":
|
|
909
|
+
action = f"Apply patch `{f.patch_id}`" if f.patch_id else "Manual fix required"
|
|
910
|
+
lines.append(f"1. **{f.title}** - {action}")
|
|
911
|
+
lines.append("")
|
|
912
|
+
|
|
913
|
+
if self.data.high_count > 0:
|
|
914
|
+
lines.extend(
|
|
915
|
+
[
|
|
916
|
+
"### ⚠️ Should Address",
|
|
917
|
+
"",
|
|
918
|
+
]
|
|
919
|
+
)
|
|
920
|
+
for f in self.data.findings:
|
|
921
|
+
if f.severity == "high":
|
|
922
|
+
lines.append(f"- {f.title}")
|
|
923
|
+
lines.append("")
|
|
924
|
+
|
|
925
|
+
# General recommendations
|
|
926
|
+
lines.extend(
|
|
927
|
+
[
|
|
928
|
+
"### 📋 General",
|
|
929
|
+
"",
|
|
930
|
+
]
|
|
931
|
+
)
|
|
932
|
+
|
|
933
|
+
if self.data.generated_tests:
|
|
934
|
+
lines.append("- Review and integrate generated tests to improve coverage")
|
|
935
|
+
|
|
936
|
+
if self.data.patches:
|
|
937
|
+
lines.append("- Review suggested patches before applying")
|
|
938
|
+
|
|
939
|
+
if self.data.coverage_after and self.data.coverage_after < 80:
|
|
940
|
+
lines.append(
|
|
941
|
+
f"- Consider increasing test coverage (currently {self.data.coverage_after:.1f}%)"
|
|
942
|
+
)
|
|
943
|
+
|
|
944
|
+
lines.append("")
|
|
945
|
+
return "\n".join(lines)
|
|
946
|
+
|
|
947
|
+
def _appendix(self) -> str:
|
|
948
|
+
"""Generate appendix section."""
|
|
949
|
+
lines = []
|
|
950
|
+
|
|
951
|
+
if self.data.blocked_operations:
|
|
952
|
+
lines.extend(
|
|
953
|
+
[
|
|
954
|
+
"## Appendix A: Blocked Operations",
|
|
955
|
+
"",
|
|
956
|
+
"The following operations were blocked to maintain repo integrity:",
|
|
957
|
+
"",
|
|
958
|
+
]
|
|
959
|
+
)
|
|
960
|
+
for op in self.data.blocked_operations:
|
|
961
|
+
lines.append(f"- `{op}`")
|
|
962
|
+
lines.append("")
|
|
963
|
+
|
|
964
|
+
if self.data.errors:
|
|
965
|
+
lines.extend(
|
|
966
|
+
[
|
|
967
|
+
"## Appendix B: Errors",
|
|
968
|
+
"",
|
|
969
|
+
"The following errors occurred during the investigation:",
|
|
970
|
+
"",
|
|
971
|
+
]
|
|
972
|
+
)
|
|
973
|
+
for err in self.data.errors:
|
|
974
|
+
lines.append(f"- {err}")
|
|
975
|
+
lines.append("")
|
|
976
|
+
|
|
977
|
+
return "\n".join(lines) if lines else ""
|
|
978
|
+
|
|
979
|
+
def _footer(self) -> str:
|
|
980
|
+
"""Generate report footer."""
|
|
981
|
+
return """---
|
|
982
|
+
|
|
983
|
+
*Generated by SuperQode - Agentic Quality Engineering*
|
|
984
|
+
|
|
985
|
+
All changes made during this investigation have been reverted.
|
|
986
|
+
Artifacts are preserved in `.superqode/qe-artifacts/` for review and integration.
|
|
987
|
+
"""
|
|
988
|
+
|
|
989
|
+
def save(self, output_dir: Path, formats: List[str] = None) -> Dict[str, Path]:
|
|
990
|
+
"""
|
|
991
|
+
Save QIR to files.
|
|
992
|
+
|
|
993
|
+
Args:
|
|
994
|
+
output_dir: Directory to save to
|
|
995
|
+
formats: List of formats ("md", "json"). Default: both
|
|
996
|
+
|
|
997
|
+
Returns:
|
|
998
|
+
Dict mapping format to file path
|
|
999
|
+
"""
|
|
1000
|
+
formats = formats or ["md", "json"]
|
|
1001
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
1002
|
+
|
|
1003
|
+
timestamp = self.data.started_at.strftime("%Y-%m-%d")
|
|
1004
|
+
base_name = f"qr-{timestamp}-{self.data.session_id[:8]}"
|
|
1005
|
+
|
|
1006
|
+
saved = {}
|
|
1007
|
+
|
|
1008
|
+
if "md" in formats:
|
|
1009
|
+
md_path = output_dir / f"{base_name}.md"
|
|
1010
|
+
md_path.write_text(self.generate_markdown())
|
|
1011
|
+
saved["md"] = md_path
|
|
1012
|
+
|
|
1013
|
+
if "json" in formats:
|
|
1014
|
+
json_path = output_dir / f"{base_name}.json"
|
|
1015
|
+
json_path.write_text(json.dumps(self.generate_json(), indent=2))
|
|
1016
|
+
saved["json"] = json_path
|
|
1017
|
+
|
|
1018
|
+
return saved
|