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.
Files changed (95) hide show
  1. devsquad-3.6.0.dist-info/METADATA +944 -0
  2. devsquad-3.6.0.dist-info/RECORD +95 -0
  3. devsquad-3.6.0.dist-info/WHEEL +5 -0
  4. devsquad-3.6.0.dist-info/entry_points.txt +2 -0
  5. devsquad-3.6.0.dist-info/licenses/LICENSE +21 -0
  6. devsquad-3.6.0.dist-info/top_level.txt +2 -0
  7. scripts/__init__.py +0 -0
  8. scripts/ai_semantic_matcher.py +512 -0
  9. scripts/alert_manager.py +505 -0
  10. scripts/api/__init__.py +43 -0
  11. scripts/api/models.py +386 -0
  12. scripts/api/routes/__init__.py +20 -0
  13. scripts/api/routes/dispatch.py +348 -0
  14. scripts/api/routes/lifecycle.py +330 -0
  15. scripts/api/routes/metrics_gates.py +347 -0
  16. scripts/api_server.py +318 -0
  17. scripts/auth.py +451 -0
  18. scripts/cli/__init__.py +1 -0
  19. scripts/cli/cli_visual.py +642 -0
  20. scripts/cli.py +1094 -0
  21. scripts/collaboration/__init__.py +212 -0
  22. scripts/collaboration/_version.py +1 -0
  23. scripts/collaboration/agent_briefing.py +656 -0
  24. scripts/collaboration/ai_semantic_matcher.py +260 -0
  25. scripts/collaboration/anchor_checker.py +281 -0
  26. scripts/collaboration/anti_rationalization.py +470 -0
  27. scripts/collaboration/async_integration_example.py +255 -0
  28. scripts/collaboration/batch_scheduler.py +149 -0
  29. scripts/collaboration/checkpoint_manager.py +561 -0
  30. scripts/collaboration/ci_feedback_adapter.py +351 -0
  31. scripts/collaboration/code_map_generator.py +247 -0
  32. scripts/collaboration/concern_pack_loader.py +352 -0
  33. scripts/collaboration/confidence_score.py +496 -0
  34. scripts/collaboration/config_loader.py +188 -0
  35. scripts/collaboration/consensus.py +244 -0
  36. scripts/collaboration/context_compressor.py +533 -0
  37. scripts/collaboration/coordinator.py +668 -0
  38. scripts/collaboration/dispatcher.py +1636 -0
  39. scripts/collaboration/dual_layer_context.py +128 -0
  40. scripts/collaboration/enhanced_worker.py +539 -0
  41. scripts/collaboration/feature_usage_tracker.py +206 -0
  42. scripts/collaboration/five_axis_consensus.py +334 -0
  43. scripts/collaboration/input_validator.py +401 -0
  44. scripts/collaboration/integration_example.py +287 -0
  45. scripts/collaboration/intent_workflow_mapper.py +350 -0
  46. scripts/collaboration/language_parsers.py +269 -0
  47. scripts/collaboration/lifecycle_protocol.py +1446 -0
  48. scripts/collaboration/llm_backend.py +453 -0
  49. scripts/collaboration/llm_cache.py +448 -0
  50. scripts/collaboration/llm_cache_async.py +347 -0
  51. scripts/collaboration/llm_retry.py +387 -0
  52. scripts/collaboration/llm_retry_async.py +389 -0
  53. scripts/collaboration/mce_adapter.py +597 -0
  54. scripts/collaboration/memory_bridge.py +1607 -0
  55. scripts/collaboration/models.py +537 -0
  56. scripts/collaboration/null_providers.py +297 -0
  57. scripts/collaboration/operation_classifier.py +289 -0
  58. scripts/collaboration/output_slicer.py +225 -0
  59. scripts/collaboration/performance_monitor.py +462 -0
  60. scripts/collaboration/permission_guard.py +865 -0
  61. scripts/collaboration/prompt_assembler.py +756 -0
  62. scripts/collaboration/prompt_variant_generator.py +483 -0
  63. scripts/collaboration/protocols.py +267 -0
  64. scripts/collaboration/report_formatter.py +352 -0
  65. scripts/collaboration/retrospective.py +279 -0
  66. scripts/collaboration/role_matcher.py +92 -0
  67. scripts/collaboration/role_template_market.py +352 -0
  68. scripts/collaboration/rule_collector.py +678 -0
  69. scripts/collaboration/scratchpad.py +346 -0
  70. scripts/collaboration/skill_registry.py +151 -0
  71. scripts/collaboration/skillifier.py +878 -0
  72. scripts/collaboration/standardized_role_template.py +317 -0
  73. scripts/collaboration/task_completion_checker.py +237 -0
  74. scripts/collaboration/test_quality_guard.py +695 -0
  75. scripts/collaboration/unified_gate_engine.py +598 -0
  76. scripts/collaboration/usage_tracker.py +309 -0
  77. scripts/collaboration/user_friendly_error.py +176 -0
  78. scripts/collaboration/verification_gate.py +312 -0
  79. scripts/collaboration/warmup_manager.py +635 -0
  80. scripts/collaboration/worker.py +513 -0
  81. scripts/collaboration/workflow_engine.py +684 -0
  82. scripts/dashboard.py +1088 -0
  83. scripts/generate_benchmark_report.py +786 -0
  84. scripts/history_manager.py +604 -0
  85. scripts/mcp_server.py +289 -0
  86. skills/__init__.py +32 -0
  87. skills/dispatch/handler.py +52 -0
  88. skills/intent/handler.py +59 -0
  89. skills/registry.py +67 -0
  90. skills/retrospective/__init__.py +0 -0
  91. skills/retrospective/handler.py +125 -0
  92. skills/review/handler.py +356 -0
  93. skills/security/handler.py +454 -0
  94. skills/test/__init__.py +0 -0
  95. 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