devsquad 3.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- devsquad-3.6.0.dist-info/METADATA +944 -0
- devsquad-3.6.0.dist-info/RECORD +95 -0
- devsquad-3.6.0.dist-info/WHEEL +5 -0
- devsquad-3.6.0.dist-info/entry_points.txt +2 -0
- devsquad-3.6.0.dist-info/licenses/LICENSE +21 -0
- devsquad-3.6.0.dist-info/top_level.txt +2 -0
- scripts/__init__.py +0 -0
- scripts/ai_semantic_matcher.py +512 -0
- scripts/alert_manager.py +505 -0
- scripts/api/__init__.py +43 -0
- scripts/api/models.py +386 -0
- scripts/api/routes/__init__.py +20 -0
- scripts/api/routes/dispatch.py +348 -0
- scripts/api/routes/lifecycle.py +330 -0
- scripts/api/routes/metrics_gates.py +347 -0
- scripts/api_server.py +318 -0
- scripts/auth.py +451 -0
- scripts/cli/__init__.py +1 -0
- scripts/cli/cli_visual.py +642 -0
- scripts/cli.py +1094 -0
- scripts/collaboration/__init__.py +212 -0
- scripts/collaboration/_version.py +1 -0
- scripts/collaboration/agent_briefing.py +656 -0
- scripts/collaboration/ai_semantic_matcher.py +260 -0
- scripts/collaboration/anchor_checker.py +281 -0
- scripts/collaboration/anti_rationalization.py +470 -0
- scripts/collaboration/async_integration_example.py +255 -0
- scripts/collaboration/batch_scheduler.py +149 -0
- scripts/collaboration/checkpoint_manager.py +561 -0
- scripts/collaboration/ci_feedback_adapter.py +351 -0
- scripts/collaboration/code_map_generator.py +247 -0
- scripts/collaboration/concern_pack_loader.py +352 -0
- scripts/collaboration/confidence_score.py +496 -0
- scripts/collaboration/config_loader.py +188 -0
- scripts/collaboration/consensus.py +244 -0
- scripts/collaboration/context_compressor.py +533 -0
- scripts/collaboration/coordinator.py +668 -0
- scripts/collaboration/dispatcher.py +1636 -0
- scripts/collaboration/dual_layer_context.py +128 -0
- scripts/collaboration/enhanced_worker.py +539 -0
- scripts/collaboration/feature_usage_tracker.py +206 -0
- scripts/collaboration/five_axis_consensus.py +334 -0
- scripts/collaboration/input_validator.py +401 -0
- scripts/collaboration/integration_example.py +287 -0
- scripts/collaboration/intent_workflow_mapper.py +350 -0
- scripts/collaboration/language_parsers.py +269 -0
- scripts/collaboration/lifecycle_protocol.py +1446 -0
- scripts/collaboration/llm_backend.py +453 -0
- scripts/collaboration/llm_cache.py +448 -0
- scripts/collaboration/llm_cache_async.py +347 -0
- scripts/collaboration/llm_retry.py +387 -0
- scripts/collaboration/llm_retry_async.py +389 -0
- scripts/collaboration/mce_adapter.py +597 -0
- scripts/collaboration/memory_bridge.py +1607 -0
- scripts/collaboration/models.py +537 -0
- scripts/collaboration/null_providers.py +297 -0
- scripts/collaboration/operation_classifier.py +289 -0
- scripts/collaboration/output_slicer.py +225 -0
- scripts/collaboration/performance_monitor.py +462 -0
- scripts/collaboration/permission_guard.py +865 -0
- scripts/collaboration/prompt_assembler.py +756 -0
- scripts/collaboration/prompt_variant_generator.py +483 -0
- scripts/collaboration/protocols.py +267 -0
- scripts/collaboration/report_formatter.py +352 -0
- scripts/collaboration/retrospective.py +279 -0
- scripts/collaboration/role_matcher.py +92 -0
- scripts/collaboration/role_template_market.py +352 -0
- scripts/collaboration/rule_collector.py +678 -0
- scripts/collaboration/scratchpad.py +346 -0
- scripts/collaboration/skill_registry.py +151 -0
- scripts/collaboration/skillifier.py +878 -0
- scripts/collaboration/standardized_role_template.py +317 -0
- scripts/collaboration/task_completion_checker.py +237 -0
- scripts/collaboration/test_quality_guard.py +695 -0
- scripts/collaboration/unified_gate_engine.py +598 -0
- scripts/collaboration/usage_tracker.py +309 -0
- scripts/collaboration/user_friendly_error.py +176 -0
- scripts/collaboration/verification_gate.py +312 -0
- scripts/collaboration/warmup_manager.py +635 -0
- scripts/collaboration/worker.py +513 -0
- scripts/collaboration/workflow_engine.py +684 -0
- scripts/dashboard.py +1088 -0
- scripts/generate_benchmark_report.py +786 -0
- scripts/history_manager.py +604 -0
- scripts/mcp_server.py +289 -0
- skills/__init__.py +32 -0
- skills/dispatch/handler.py +52 -0
- skills/intent/handler.py +59 -0
- skills/registry.py +67 -0
- skills/retrospective/__init__.py +0 -0
- skills/retrospective/handler.py +125 -0
- skills/review/handler.py +356 -0
- skills/security/handler.py +454 -0
- skills/test/__init__.py +0 -0
- skills/test/handler.py +78 -0
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
UnifiedGateEngine - Unified gate engine for layered architecture (Plan C)
|
|
5
|
+
|
|
6
|
+
Integrates:
|
|
7
|
+
- VerificationGate: Worker output quality validation
|
|
8
|
+
- LifecycleProtocol: Phase transition gates
|
|
9
|
+
- CheckpointManager: State persistence
|
|
10
|
+
|
|
11
|
+
Provides single entry point for all gate checks in DevSquad.
|
|
12
|
+
|
|
13
|
+
Spec reference: docs/spec/SPEC_Lifecycle_Unified_Architecture_C.md
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
from enum import Enum
|
|
20
|
+
from typing import Any, Dict, List, Optional, Callable
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class GateType(Enum):
|
|
26
|
+
"""Types of gates in the unified system."""
|
|
27
|
+
PHASE_TRANSITION = "phase_transition"
|
|
28
|
+
WORKER_OUTPUT = "worker_output"
|
|
29
|
+
QUALITY_THRESHOLD = "quality_threshold"
|
|
30
|
+
SECURITY_CHECK = "security_check"
|
|
31
|
+
COMPLIANCE_CHECK = "compliance_check"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class GateSeverity(Enum):
|
|
35
|
+
"""Severity levels for gate results."""
|
|
36
|
+
CRITICAL = "critical"
|
|
37
|
+
WARNING = "warning"
|
|
38
|
+
INFO = "info"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class UnifiedGateConfig:
|
|
43
|
+
"""Configuration for unified gate engine."""
|
|
44
|
+
strict_mode: bool = True
|
|
45
|
+
enable_verification_gate: bool = True
|
|
46
|
+
enable_phase_gate: bool = True
|
|
47
|
+
enable_security_scan: bool = False
|
|
48
|
+
enable_compliance_check: bool = False
|
|
49
|
+
max_output_lines: int = 100
|
|
50
|
+
min_test_coverage: float = 0.8
|
|
51
|
+
allowed_critical_flags: int = 0
|
|
52
|
+
auto_fix_warnings: bool = False
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class UnifiedGateResult:
|
|
57
|
+
"""Result from unified gate engine check."""
|
|
58
|
+
passed: bool
|
|
59
|
+
gate_type: GateType
|
|
60
|
+
verdict: str # APPROVE / CONDITIONAL / REJECT
|
|
61
|
+
severity: GateSeverity
|
|
62
|
+
checks_run: int = 0
|
|
63
|
+
checks_passed: int = 0
|
|
64
|
+
critical_issues: List[Dict[str, Any]] = field(default_factory=list)
|
|
65
|
+
warnings: List[Dict[str, Any]] = field(default_factory=list)
|
|
66
|
+
suggestions: List[str] = field(default_factory=list)
|
|
67
|
+
evidence_required: List[str] = field(default_factory=list)
|
|
68
|
+
execution_time_ms: float = 0.0
|
|
69
|
+
timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
|
|
70
|
+
|
|
71
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
72
|
+
return {
|
|
73
|
+
"passed": self.passed,
|
|
74
|
+
"gate_type": self.gate_type.value,
|
|
75
|
+
"verdict": self.verdict,
|
|
76
|
+
"severity": self.severity.value,
|
|
77
|
+
"checks_run": self.checks_run,
|
|
78
|
+
"checks_passed": self.checks_passed,
|
|
79
|
+
"critical_issues_count": len(self.critical_issues),
|
|
80
|
+
"warnings_count": len(self.warnings),
|
|
81
|
+
"evidence_required_count": len(self.evidence_required),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
def to_summary(self) -> str:
|
|
85
|
+
lines = [
|
|
86
|
+
f"Gate Result [{self.gate_type.value}]: {self.verdict}",
|
|
87
|
+
f"Status: {'✅ PASSED' if self.passed else '❌ FAILED'}",
|
|
88
|
+
f"Checks: {self.checks_passed}/{self.checks_run}",
|
|
89
|
+
]
|
|
90
|
+
if self.critical_issues:
|
|
91
|
+
lines.append(f"Critical Issues: {len(self.critical_issues)}")
|
|
92
|
+
if self.warnings:
|
|
93
|
+
lines.append(f"Warnings: {len(self.warnings)}")
|
|
94
|
+
if self.evidence_required:
|
|
95
|
+
lines.append(f"Evidence Required: {', '.join(self.evidence_required[:3])}")
|
|
96
|
+
return "\n".join(lines)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass
|
|
100
|
+
class PhaseGateContext:
|
|
101
|
+
"""Context for phase transition gate check."""
|
|
102
|
+
phase_id: str
|
|
103
|
+
phase_name: str
|
|
104
|
+
current_state: str
|
|
105
|
+
target_state: str
|
|
106
|
+
dependencies_met: bool = False
|
|
107
|
+
completed_phases: List[str] = field(default_factory=list)
|
|
108
|
+
artifacts_available: Dict[str, bool] = field(default_factory=dict)
|
|
109
|
+
reviewers_approved: List[str] = field(default_factory=list)
|
|
110
|
+
custom_conditions: Dict[str, Any] = field(default_factory=dict)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@dataclass
|
|
114
|
+
class WorkerOutputContext:
|
|
115
|
+
"""Context for worker output quality gate check."""
|
|
116
|
+
role_id: str
|
|
117
|
+
task_description: str
|
|
118
|
+
output: str
|
|
119
|
+
has_code_changes: bool = False
|
|
120
|
+
has_test_changes: bool = False
|
|
121
|
+
test_results: Optional[Dict[str, Any]] = None
|
|
122
|
+
build_status: Optional[Dict[str, Any]] = None
|
|
123
|
+
diff_summary: Optional[Dict[str, Any]] = None
|
|
124
|
+
coverage_delta: float = 0.0
|
|
125
|
+
claims_complete: bool = False
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class UnifiedGateEngine:
|
|
129
|
+
"""
|
|
130
|
+
Unified gate engine for DevSquad layered architecture.
|
|
131
|
+
|
|
132
|
+
Responsibilities:
|
|
133
|
+
1. Phase transition validation (lifecycle protocol)
|
|
134
|
+
2. Worker output quality verification (verification gate)
|
|
135
|
+
3. Security and compliance checks (optional)
|
|
136
|
+
4. Evidence collection and validation
|
|
137
|
+
5. Integration with checkpoint manager for state persistence
|
|
138
|
+
|
|
139
|
+
Design Principles:
|
|
140
|
+
- Single entry point for all gate checks
|
|
141
|
+
- Pluggable checkers for different gate types
|
|
142
|
+
- Configurable strictness levels
|
|
143
|
+
- Comprehensive result reporting
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
def __init__(self, config: Optional[UnifiedGateConfig] = None):
|
|
147
|
+
"""
|
|
148
|
+
Initialize unified gate engine.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
config: Configuration for gate behavior
|
|
152
|
+
"""
|
|
153
|
+
self.config = config or UnifiedGateConfig()
|
|
154
|
+
self._checkers: Dict[GateType, Callable] = {
|
|
155
|
+
GateType.PHASE_TRANSITION: self._check_phase_transition,
|
|
156
|
+
GateType.WORKER_OUTPUT: self._check_worker_output,
|
|
157
|
+
}
|
|
158
|
+
self._custom_checkers: Dict[GateType, List[Callable]] = {}
|
|
159
|
+
self._statistics: Dict[str, int] = {
|
|
160
|
+
"total_checks": 0,
|
|
161
|
+
"passed": 0,
|
|
162
|
+
"failed": 0,
|
|
163
|
+
"conditional": 0,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
def register_checker(
|
|
167
|
+
self,
|
|
168
|
+
gate_type: GateType,
|
|
169
|
+
checker: Callable,
|
|
170
|
+
prepend: bool = False,
|
|
171
|
+
) -> None:
|
|
172
|
+
"""
|
|
173
|
+
Register a custom checker for a gate type.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
gate_type: Type of gate this checker handles
|
|
177
|
+
checker: Callable that takes context and returns partial result
|
|
178
|
+
prepend: If True, add to front of checker list
|
|
179
|
+
"""
|
|
180
|
+
if gate_type not in self._custom_checkers:
|
|
181
|
+
self._custom_checkers[gate_type] = []
|
|
182
|
+
|
|
183
|
+
if prepend:
|
|
184
|
+
self._custom_checkers[gate_type].insert(0, checker)
|
|
185
|
+
else:
|
|
186
|
+
self._custom_checkers[gate_type].append(checker)
|
|
187
|
+
|
|
188
|
+
logger.debug("Registered custom checker for %s", gate_type.value)
|
|
189
|
+
|
|
190
|
+
def check(
|
|
191
|
+
self,
|
|
192
|
+
gate_type: GateType,
|
|
193
|
+
context: Any,
|
|
194
|
+
**kwargs,
|
|
195
|
+
) -> UnifiedGateResult:
|
|
196
|
+
"""
|
|
197
|
+
Run unified gate check.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
gate_type: Type of gate to check
|
|
201
|
+
context: Context data (PhaseGateContext or WorkerOutputContext)
|
|
202
|
+
**kwargs: Additional parameters
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
UnifiedGateResult with comprehensive check results
|
|
206
|
+
"""
|
|
207
|
+
start_time = datetime.now()
|
|
208
|
+
self._statistics["total_checks"] += 1
|
|
209
|
+
|
|
210
|
+
logger.info(
|
|
211
|
+
"Running %s gate check (strict=%s)",
|
|
212
|
+
gate_type.value,
|
|
213
|
+
self.config.strict_mode,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
try:
|
|
217
|
+
base_checker = self._checkers.get(gate_type)
|
|
218
|
+
if not base_checker:
|
|
219
|
+
return UnifiedGateResult(
|
|
220
|
+
passed=False,
|
|
221
|
+
gate_type=gate_type,
|
|
222
|
+
verdict="REJECT",
|
|
223
|
+
severity=GateSeverity.CRITICAL,
|
|
224
|
+
critical_issues=[{
|
|
225
|
+
"code": "UNKNOWN_GATE_TYPE",
|
|
226
|
+
"message": f"No checker registered for {gate_type.value}",
|
|
227
|
+
}],
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
result = base_checker(context, **kwargs)
|
|
231
|
+
|
|
232
|
+
# Run custom checkers if any
|
|
233
|
+
custom_results = []
|
|
234
|
+
for checker in self._custom_checkers.get(gate_type, []):
|
|
235
|
+
try:
|
|
236
|
+
custom_result = checker(context, **kwargs)
|
|
237
|
+
if isinstance(custom_result, dict):
|
|
238
|
+
custom_results.append(custom_result)
|
|
239
|
+
except Exception as e:
|
|
240
|
+
logger.warning("Custom checker error: %s", e)
|
|
241
|
+
|
|
242
|
+
# Merge custom results
|
|
243
|
+
if custom_results:
|
|
244
|
+
self._merge_custom_results(result, custom_results)
|
|
245
|
+
|
|
246
|
+
# Update statistics
|
|
247
|
+
elapsed = (datetime.now() - start_time).total_seconds() * 1000
|
|
248
|
+
result.execution_time_ms = elapsed
|
|
249
|
+
|
|
250
|
+
if result.passed:
|
|
251
|
+
self._statistics["passed"] += 1
|
|
252
|
+
elif result.verdict == "CONDITIONAL":
|
|
253
|
+
self._statistics["conditional"] += 1
|
|
254
|
+
else:
|
|
255
|
+
self._statistics["failed"] += 1
|
|
256
|
+
|
|
257
|
+
logger.info(
|
|
258
|
+
"Gate %s completed: %s (%d issues)",
|
|
259
|
+
gate_type.value,
|
|
260
|
+
result.verdict,
|
|
261
|
+
len(result.critical_issues),
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
return result
|
|
265
|
+
|
|
266
|
+
except Exception as e:
|
|
267
|
+
logger.error("Gate check failed with exception: %s", e)
|
|
268
|
+
return UnifiedGateResult(
|
|
269
|
+
passed=False,
|
|
270
|
+
gate_type=gate_type,
|
|
271
|
+
verdict="REJECT",
|
|
272
|
+
severity=GateSeverity.CRITICAL,
|
|
273
|
+
critical_issues=[{
|
|
274
|
+
"code": "GATE_EXCEPTION",
|
|
275
|
+
"message": f"Gate check exception: {str(e)[:200]}",
|
|
276
|
+
}],
|
|
277
|
+
execution_time_ms=(datetime.now() - start_time).total_seconds() * 1000,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
def _merge_custom_results(
|
|
281
|
+
self,
|
|
282
|
+
result: UnifiedGateResult,
|
|
283
|
+
custom_results: List[Dict[str, Any]],
|
|
284
|
+
) -> None:
|
|
285
|
+
"""Merge custom checker results into main result."""
|
|
286
|
+
for custom in custom_results:
|
|
287
|
+
if custom.get("critical_issues"):
|
|
288
|
+
result.critical_issues.extend(custom["critical_issues"])
|
|
289
|
+
if custom.get("warnings"):
|
|
290
|
+
result.warnings.extend(custom["warnings"])
|
|
291
|
+
if custom.get("suggestions"):
|
|
292
|
+
result.suggestions.extend(custom["suggestions"])
|
|
293
|
+
if custom.get("evidence_required"):
|
|
294
|
+
result.evidence_required.extend(custom["evidence_required"])
|
|
295
|
+
|
|
296
|
+
# Recalculate verdict based on merged results
|
|
297
|
+
if result.critical_issues:
|
|
298
|
+
result.passed = False
|
|
299
|
+
result.verdict = "REJECT"
|
|
300
|
+
result.severity = GateSeverity.CRITICAL
|
|
301
|
+
elif result.warnings or not result.evidence_required:
|
|
302
|
+
result.verdict = "CONDITIONAL"
|
|
303
|
+
result.severity = GateSeverity.WARNING
|
|
304
|
+
|
|
305
|
+
def _check_phase_transition(
|
|
306
|
+
self,
|
|
307
|
+
context: PhaseGateContext,
|
|
308
|
+
**kwargs,
|
|
309
|
+
) -> UnifiedGateResult:
|
|
310
|
+
"""
|
|
311
|
+
Check phase transition gate conditions.
|
|
312
|
+
|
|
313
|
+
Validates:
|
|
314
|
+
1. Dependencies are met
|
|
315
|
+
2. Required artifacts are available
|
|
316
|
+
3. Reviewer approvals (if required)
|
|
317
|
+
4. Custom phase conditions
|
|
318
|
+
"""
|
|
319
|
+
checks_run = 0
|
|
320
|
+
checks_passed = 0
|
|
321
|
+
critical_issues = []
|
|
322
|
+
warnings = []
|
|
323
|
+
evidence_required = []
|
|
324
|
+
|
|
325
|
+
# Check 1: Dependencies
|
|
326
|
+
checks_run += 1
|
|
327
|
+
if context.dependencies_met:
|
|
328
|
+
checks_passed += 1
|
|
329
|
+
else:
|
|
330
|
+
missing_deps = [
|
|
331
|
+
d for d in getattr(context, 'unmet_dependencies', [])
|
|
332
|
+
]
|
|
333
|
+
if missing_deps:
|
|
334
|
+
critical_issues.append({
|
|
335
|
+
"code": "UNMET_DEPENDENCIES",
|
|
336
|
+
"message": f"Unmet dependencies: {', '.join(missing_deps)}",
|
|
337
|
+
"dependencies": missing_deps,
|
|
338
|
+
})
|
|
339
|
+
else:
|
|
340
|
+
warnings.append({
|
|
341
|
+
"code": "DEPENDENCY_CHECK_SKIPPED",
|
|
342
|
+
"message": "Dependency status unknown",
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
# Check 2: Artifacts
|
|
346
|
+
checks_run += 1
|
|
347
|
+
if context.artifacts_available:
|
|
348
|
+
missing_artifacts = [
|
|
349
|
+
k for k, v in context.artifacts_available.items() if not v
|
|
350
|
+
]
|
|
351
|
+
if not missing_artifacts:
|
|
352
|
+
checks_passed += 1
|
|
353
|
+
else:
|
|
354
|
+
evidence_required.extend(missing_artifacts)
|
|
355
|
+
warnings.append({
|
|
356
|
+
"code": "MISSING_ARTIFACTS",
|
|
357
|
+
"message": f"Missing artifacts: {', '.join(missing_artifacts)}",
|
|
358
|
+
"artifacts": missing_artifacts,
|
|
359
|
+
})
|
|
360
|
+
else:
|
|
361
|
+
checks_passed += 1 # No artifacts required
|
|
362
|
+
|
|
363
|
+
# Check 3: Reviewer approvals (if phase requires it)
|
|
364
|
+
if context.reviewers_approved is not None:
|
|
365
|
+
checks_run += 1
|
|
366
|
+
if len(context.reviewers_approved) >= 1:
|
|
367
|
+
checks_passed += 1
|
|
368
|
+
else:
|
|
369
|
+
warnings.append({
|
|
370
|
+
"code": "NO_REVIEWER_APPROVAL",
|
|
371
|
+
"message": "No reviewer approvals yet",
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
# Determine verdict
|
|
375
|
+
passed = len(critical_issues) == 0
|
|
376
|
+
if critical_issues:
|
|
377
|
+
verdict = "REJECT"
|
|
378
|
+
severity = GateSeverity.CRITICAL
|
|
379
|
+
elif warnings or evidence_required:
|
|
380
|
+
verdict = "CONDITIONAL"
|
|
381
|
+
severity = GateSeverity.WARNING
|
|
382
|
+
else:
|
|
383
|
+
verdict = "APPROVE"
|
|
384
|
+
severity = GateSeverity.INFO
|
|
385
|
+
|
|
386
|
+
return UnifiedGateResult(
|
|
387
|
+
passed=passed,
|
|
388
|
+
gate_type=GateType.PHASE_TRANSITION,
|
|
389
|
+
verdict=verdict,
|
|
390
|
+
severity=severity,
|
|
391
|
+
checks_run=checks_run,
|
|
392
|
+
checks_passed=checks_passed,
|
|
393
|
+
critical_issues=critical_issues,
|
|
394
|
+
warnings=warnings,
|
|
395
|
+
evidence_required=evidence_required,
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
def _check_worker_output(
|
|
399
|
+
self,
|
|
400
|
+
context: WorkerOutputContext,
|
|
401
|
+
**kwargs,
|
|
402
|
+
) -> UnifiedGateResult:
|
|
403
|
+
"""
|
|
404
|
+
Check worker output quality using VerificationGate logic.
|
|
405
|
+
|
|
406
|
+
Validates:
|
|
407
|
+
1. Code changes have corresponding tests
|
|
408
|
+
2. Bug fixes have reproduction tests
|
|
409
|
+
3. No tests skipped/disabled
|
|
410
|
+
4. Coverage didn't decrease significantly
|
|
411
|
+
5. Output size within limits
|
|
412
|
+
6. Required evidence provided
|
|
413
|
+
"""
|
|
414
|
+
checks_run = 0
|
|
415
|
+
checks_passed = 0
|
|
416
|
+
critical_issues = []
|
|
417
|
+
warnings = []
|
|
418
|
+
evidence_required = []
|
|
419
|
+
|
|
420
|
+
# Import VerificationGate if available
|
|
421
|
+
try:
|
|
422
|
+
from scripts.collaboration.verification_gate import (
|
|
423
|
+
VerificationGate,
|
|
424
|
+
get_shared_gate,
|
|
425
|
+
)
|
|
426
|
+
vg = get_shared_gate(strict_mode=self.config.strict_mode)
|
|
427
|
+
|
|
428
|
+
from scripts.collaboration.verification_gate import CompletionContext
|
|
429
|
+
vg_context = CompletionContext(
|
|
430
|
+
role_id=context.role_id,
|
|
431
|
+
has_code_changes=context.has_code_changes,
|
|
432
|
+
has_test_changes=context.has_test_changes,
|
|
433
|
+
is_bug_fix=self._is_likely_bug_fix(context.task_description),
|
|
434
|
+
test_run_count=1 if context.test_results else 0,
|
|
435
|
+
all_passed=context.test_results.get("all_passed", False) if context.test_results else False,
|
|
436
|
+
tests_skipped=context.test_results.get("skipped", 0) if context.test_results else 0,
|
|
437
|
+
coverage_delta=context.coverage_delta,
|
|
438
|
+
output_lines=len(context.output.split("\n")) if context.output else 0,
|
|
439
|
+
was_sliced=len(context.output.split("\n")) > self.config.max_output_lines if context.output else False,
|
|
440
|
+
claims_complete=context.claims_complete,
|
|
441
|
+
evidence=self._extract_evidence(context),
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
vg_result = vg.check(vg_context)
|
|
445
|
+
|
|
446
|
+
# Convert VerificationGate result to UnifiedGateResult format
|
|
447
|
+
if vg_result.red_flags:
|
|
448
|
+
for flag in vg_result.red_flags:
|
|
449
|
+
issue = {
|
|
450
|
+
"code": flag.id,
|
|
451
|
+
"message": flag.description,
|
|
452
|
+
"severity": flag.severity,
|
|
453
|
+
}
|
|
454
|
+
if flag.severity == "critical":
|
|
455
|
+
critical_issues.append(issue)
|
|
456
|
+
else:
|
|
457
|
+
warnings.append(issue)
|
|
458
|
+
|
|
459
|
+
if vg_result.missing_evidence:
|
|
460
|
+
for item in vg_result.missing_evidence:
|
|
461
|
+
evidence_required.append(item.key)
|
|
462
|
+
|
|
463
|
+
checks_run = vg.red_flag_count + vg.evidence_item_count
|
|
464
|
+
checks_passed = checks_run - len(critical_issues) - len(warnings)
|
|
465
|
+
|
|
466
|
+
except ImportError:
|
|
467
|
+
logger.warning("VerificationGate not available, using basic checks")
|
|
468
|
+
critical_issues, warnings, evidence_required, checks_run, checks_passed = \
|
|
469
|
+
self._basic_worker_output_checks(context)
|
|
470
|
+
|
|
471
|
+
# Determine verdict
|
|
472
|
+
passed = len(critical_issues) <= self.config.allowed_critical_flags
|
|
473
|
+
if critical_issues and not passed:
|
|
474
|
+
verdict = "REJECT"
|
|
475
|
+
severity = GateSeverity.CRITICAL
|
|
476
|
+
elif warnings or evidence_required:
|
|
477
|
+
verdict = "CONDITIONAL"
|
|
478
|
+
severity = GateSeverity.WARNING
|
|
479
|
+
else:
|
|
480
|
+
verdict = "APPROVE"
|
|
481
|
+
severity = GateSeverity.INFO
|
|
482
|
+
|
|
483
|
+
return UnifiedGateResult(
|
|
484
|
+
passed=passed,
|
|
485
|
+
gate_type=GateType.WORKER_OUTPUT,
|
|
486
|
+
verdict=verdict,
|
|
487
|
+
severity=severity,
|
|
488
|
+
checks_run=checks_run,
|
|
489
|
+
checks_passed=checks_passed,
|
|
490
|
+
critical_issues=critical_issues,
|
|
491
|
+
warnings=warnings,
|
|
492
|
+
evidence_required=evidence_required,
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
def _basic_worker_output_checks(
|
|
496
|
+
self,
|
|
497
|
+
context: WorkerOutputContext,
|
|
498
|
+
) -> tuple:
|
|
499
|
+
"""Basic fallback checks when VerificationGate is unavailable."""
|
|
500
|
+
checks_run = 0
|
|
501
|
+
checks_passed = 0
|
|
502
|
+
critical_issues = []
|
|
503
|
+
warnings = []
|
|
504
|
+
evidence_required = []
|
|
505
|
+
|
|
506
|
+
# Basic check: code without tests
|
|
507
|
+
checks_run += 1
|
|
508
|
+
if context.has_code_changes and not context.has_test_changes:
|
|
509
|
+
critical_issues.append({
|
|
510
|
+
"code": "no_test_for_new_behavior",
|
|
511
|
+
"message": "Code changes without corresponding tests",
|
|
512
|
+
})
|
|
513
|
+
else:
|
|
514
|
+
checks_passed += 1
|
|
515
|
+
|
|
516
|
+
# Basic check: output size
|
|
517
|
+
checks_run += 1
|
|
518
|
+
output_lines = len(context.output.split("\n")) if context.output else 0
|
|
519
|
+
if output_lines > self.config.max_output_lines:
|
|
520
|
+
warnings.append({
|
|
521
|
+
"code": "output_exceeds_limit",
|
|
522
|
+
"message": f"Output ({output_lines} lines) exceeds limit ({self.config.max_output_lines})",
|
|
523
|
+
})
|
|
524
|
+
else:
|
|
525
|
+
checks_passed += 1
|
|
526
|
+
|
|
527
|
+
# Basic check: evidence
|
|
528
|
+
checks_run += 1
|
|
529
|
+
if context.claims_complete and not context.test_results:
|
|
530
|
+
evidence_required.append("test_results")
|
|
531
|
+
evidence_required.append("build_status")
|
|
532
|
+
else:
|
|
533
|
+
checks_passed += 1
|
|
534
|
+
|
|
535
|
+
return critical_issues, warnings, evidence_required, checks_run, checks_passed
|
|
536
|
+
|
|
537
|
+
@staticmethod
|
|
538
|
+
def _is_likely_bug_fix(task_description: str) -> bool:
|
|
539
|
+
"""Heuristically determine if task looks like a bug fix."""
|
|
540
|
+
bug_keywords = [
|
|
541
|
+
"fix", "bug", "error", "fail", "crash", "broken",
|
|
542
|
+
"修复", "错误", "失败", "崩溃", "异常",
|
|
543
|
+
]
|
|
544
|
+
return any(kw in task_description.lower() for kw in bug_keywords)
|
|
545
|
+
|
|
546
|
+
@staticmethod
|
|
547
|
+
def _extract_evidence(context: WorkerOutputContext) -> Dict[str, Any]:
|
|
548
|
+
"""Extract evidence from worker output context."""
|
|
549
|
+
evidence = {}
|
|
550
|
+
if context.test_results:
|
|
551
|
+
evidence["test_results"] = context.test_results
|
|
552
|
+
if context.build_status:
|
|
553
|
+
evidence["build_status"] = context.build_status
|
|
554
|
+
if context.diff_summary:
|
|
555
|
+
evidence["diff_summary"] = context.diff_summary
|
|
556
|
+
return evidence
|
|
557
|
+
|
|
558
|
+
def get_statistics(self) -> Dict[str, Any]:
|
|
559
|
+
"""Get gate engine statistics."""
|
|
560
|
+
total = self._statistics["total_checks"]
|
|
561
|
+
return {
|
|
562
|
+
**self._statistics,
|
|
563
|
+
"pass_rate": (
|
|
564
|
+
(self._statistics["passed"] / total * 100)
|
|
565
|
+
if total > 0
|
|
566
|
+
else 0.0
|
|
567
|
+
),
|
|
568
|
+
"custom_checkers_registered": sum(
|
|
569
|
+
len(checkers)
|
|
570
|
+
for checkers in self._custom_checkers.values()
|
|
571
|
+
),
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
def reset_statistics(self) -> None:
|
|
575
|
+
"""Reset statistics counters."""
|
|
576
|
+
self._statistics = {
|
|
577
|
+
"total_checks": 0,
|
|
578
|
+
"passed": 0,
|
|
579
|
+
"failed": 0,
|
|
580
|
+
"conditional": 0,
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def get_shared_gate_engine(
|
|
585
|
+
config: Optional[UnifiedGateConfig] = None,
|
|
586
|
+
) -> UnifiedGateEngine:
|
|
587
|
+
"""
|
|
588
|
+
Get or create shared singleton instance of UnifiedGateEngine.
|
|
589
|
+
|
|
590
|
+
Args:
|
|
591
|
+
config: Optional configuration for the engine
|
|
592
|
+
|
|
593
|
+
Returns:
|
|
594
|
+
Shared UnifiedGateEngine instance
|
|
595
|
+
"""
|
|
596
|
+
if not hasattr(get_shared_gate_engine, "_instance"):
|
|
597
|
+
get_shared_gate_engine._instance = UnifiedGateEngine(config=config)
|
|
598
|
+
return get_shared_gate_engine._instance
|