tapps-agents 3.5.39__py3-none-any.whl → 3.5.40__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 (70) hide show
  1. tapps_agents/__init__.py +2 -2
  2. tapps_agents/agents/enhancer/agent.py +2728 -2728
  3. tapps_agents/agents/implementer/agent.py +35 -13
  4. tapps_agents/agents/reviewer/agent.py +43 -10
  5. tapps_agents/agents/reviewer/scoring.py +59 -68
  6. tapps_agents/agents/reviewer/tools/__init__.py +24 -0
  7. tapps_agents/agents/reviewer/tools/ruff_grouping.py +250 -0
  8. tapps_agents/agents/reviewer/tools/scoped_mypy.py +284 -0
  9. tapps_agents/beads/__init__.py +11 -0
  10. tapps_agents/beads/hydration.py +213 -0
  11. tapps_agents/beads/specs.py +206 -0
  12. tapps_agents/cli/commands/health.py +19 -3
  13. tapps_agents/cli/commands/simple_mode.py +842 -676
  14. tapps_agents/cli/commands/task.py +219 -0
  15. tapps_agents/cli/commands/top_level.py +13 -0
  16. tapps_agents/cli/main.py +658 -651
  17. tapps_agents/cli/parsers/top_level.py +1978 -1881
  18. tapps_agents/core/config.py +1622 -1622
  19. tapps_agents/core/init_project.py +3012 -2897
  20. tapps_agents/epic/markdown_sync.py +105 -0
  21. tapps_agents/epic/orchestrator.py +1 -2
  22. tapps_agents/epic/parser.py +427 -423
  23. tapps_agents/experts/adaptive_domain_detector.py +0 -2
  24. tapps_agents/experts/knowledge/api-design-integration/api-security-patterns.md +15 -15
  25. tapps_agents/experts/knowledge/api-design-integration/external-api-integration.md +19 -44
  26. tapps_agents/health/checks/outcomes.backup_20260204_064058.py +324 -0
  27. tapps_agents/health/checks/outcomes.backup_20260204_064256.py +324 -0
  28. tapps_agents/health/checks/outcomes.backup_20260204_064600.py +324 -0
  29. tapps_agents/health/checks/outcomes.py +134 -46
  30. tapps_agents/health/orchestrator.py +12 -4
  31. tapps_agents/hooks/__init__.py +33 -0
  32. tapps_agents/hooks/config.py +140 -0
  33. tapps_agents/hooks/events.py +135 -0
  34. tapps_agents/hooks/executor.py +128 -0
  35. tapps_agents/hooks/manager.py +143 -0
  36. tapps_agents/session/__init__.py +19 -0
  37. tapps_agents/session/manager.py +256 -0
  38. tapps_agents/simple_mode/code_snippet_handler.py +382 -0
  39. tapps_agents/simple_mode/intent_parser.py +29 -4
  40. tapps_agents/simple_mode/orchestrators/base.py +185 -59
  41. tapps_agents/simple_mode/orchestrators/build_orchestrator.py +2667 -2642
  42. tapps_agents/simple_mode/orchestrators/fix_orchestrator.py +2 -2
  43. tapps_agents/simple_mode/workflow_suggester.py +37 -3
  44. tapps_agents/workflow/agent_handlers/implementer_handler.py +18 -3
  45. tapps_agents/workflow/cursor_executor.py +2196 -2118
  46. tapps_agents/workflow/direct_execution_fallback.py +16 -3
  47. tapps_agents/workflow/message_formatter.py +2 -1
  48. tapps_agents/workflow/parallel_executor.py +43 -4
  49. tapps_agents/workflow/parser.py +375 -357
  50. tapps_agents/workflow/rules_generator.py +337 -337
  51. tapps_agents/workflow/skill_invoker.py +9 -3
  52. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.40.dist-info}/METADATA +5 -1
  53. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.40.dist-info}/RECORD +57 -53
  54. tapps_agents/agents/analyst/SKILL.md +0 -85
  55. tapps_agents/agents/architect/SKILL.md +0 -80
  56. tapps_agents/agents/debugger/SKILL.md +0 -66
  57. tapps_agents/agents/designer/SKILL.md +0 -78
  58. tapps_agents/agents/documenter/SKILL.md +0 -95
  59. tapps_agents/agents/enhancer/SKILL.md +0 -189
  60. tapps_agents/agents/implementer/SKILL.md +0 -117
  61. tapps_agents/agents/improver/SKILL.md +0 -55
  62. tapps_agents/agents/ops/SKILL.md +0 -64
  63. tapps_agents/agents/orchestrator/SKILL.md +0 -238
  64. tapps_agents/agents/planner/story_template.md +0 -37
  65. tapps_agents/agents/reviewer/templates/quality-dashboard.html.j2 +0 -150
  66. tapps_agents/agents/tester/SKILL.md +0 -71
  67. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.40.dist-info}/WHEEL +0 -0
  68. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.40.dist-info}/entry_points.txt +0 -0
  69. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.40.dist-info}/licenses/LICENSE +0 -0
  70. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.40.dist-info}/top_level.txt +0 -0
@@ -47,7 +47,6 @@ class AdaptiveDomainDetector:
47
47
  "refresh token",
48
48
  "token expiry",
49
49
  "access token refresh",
50
- "zoho-oauthtoken",
51
50
  ],
52
51
  "api-clients": [
53
52
  "api client",
@@ -89,7 +88,6 @@ class AdaptiveDomainDetector:
89
88
  r"access_token",
90
89
  r"token_url",
91
90
  r"expires_in",
92
- r"Zoho-oauthtoken",
93
91
  r"Bearer\s+token",
94
92
  ],
95
93
  "api-clients": [
@@ -86,8 +86,8 @@ import requests
86
86
  class OAuth2RefreshTokenClient:
87
87
  """
88
88
  OAuth2 client using refresh-token flow for long-lived API access.
89
-
90
- This pattern is used by many SaaS APIs (Zoho, Okta, Salesforce) that require
89
+
90
+ This pattern is used by many SaaS APIs that require
91
91
  long-term access without user re-authentication.
92
92
  """
93
93
 
@@ -198,11 +198,11 @@ class OAuth2RefreshTokenClient:
198
198
 
199
199
  **Best Practices:**
200
200
  - **Refresh proactively:** Refresh tokens 60 seconds before expiry to avoid race conditions
201
- - **Handle both expiry formats:** Some providers use `expires_in_sec`, others use `expires_in` (Zoho uses both)
201
+ - **Handle both expiry formats:** Some providers use `expires_in_sec`, others use `expires_in` (handle both field names)
202
202
  - **Cache access tokens:** Store tokens until near expiry to reduce API calls
203
203
  - **Secure storage:** Use environment variables or secret managers for refresh tokens (never hardcode)
204
204
  - **Error handling:** Handle token refresh failures gracefully (retry, exponential backoff)
205
- - **Multi-region support:** Some providers (e.g. Zoho) have different endpoints for EU/US regions
205
+ - **Multi-region support:** Some providers have different endpoints for different data centers/regions
206
206
 
207
207
  **Example Usage:**
208
208
  ```python
@@ -211,8 +211,8 @@ client = OAuth2RefreshTokenClient(
211
211
  client_id=os.environ["OAUTH_CLIENT_ID"],
212
212
  client_secret=os.environ["OAUTH_CLIENT_SECRET"],
213
213
  refresh_token=os.environ["OAUTH_REFRESH_TOKEN"],
214
- token_url="https://accounts.zoho.com/oauth/v2/token",
215
- api_base_url="https://www.site24x7.com/api",
214
+ token_url="https://api.example.com/oauth/v2/token",
215
+ api_base_url="https://api.example.com/v1",
216
216
  )
217
217
 
218
218
  # Token refresh happens automatically
@@ -288,24 +288,24 @@ context.load_verify_locations('ca.crt')
288
288
  **Overview:** Some APIs use non-standard authentication headers instead of the standard `Authorization: Bearer <token>` format.
289
289
 
290
290
  **Common Custom Headers:**
291
- - `Authorization: Zoho-oauthtoken <token>` (Zoho/Site24x7)
292
291
  - `Authorization: Bearer <token>` (standard OAuth2)
293
292
  - `X-API-Key: <key>` (API key authentication)
294
293
  - `Authorization: Token <token>` (GitHub-style)
294
+ - `Authorization: <custom-prefix> <token>` (vendor-specific formats)
295
295
 
296
296
  **Implementation:**
297
297
  ```python
298
298
  def _headers(self) -> dict[str, str]:
299
299
  """
300
300
  Get HTTP headers for authenticated requests.
301
-
301
+
302
302
  Supports custom auth header formats based on API requirements.
303
303
  """
304
304
  token = self._get_access_token()
305
-
306
- # Custom header format (e.g. Zoho/Site24x7)
305
+
306
+ # Custom header format (vendor-specific)
307
307
  return {
308
- "Authorization": f"Zoho-oauthtoken {token}", # Custom header format
308
+ "Authorization": f"CustomPrefix {token}", # Replace with API-specific format
309
309
  "Accept": "application/json",
310
310
  }
311
311
 
@@ -329,14 +329,14 @@ def _headers_standard(self) -> dict[str, str]:
329
329
  ```python
330
330
  class FlexibleOAuth2Client:
331
331
  """OAuth2 client that supports multiple auth header formats."""
332
-
332
+
333
333
  def __init__(self, auth_header_format: str = "Bearer"):
334
334
  """
335
335
  Args:
336
- auth_header_format: Header format - "Bearer", "Zoho-oauthtoken", "Token", etc.
336
+ auth_header_format: Header format - "Bearer", "Token", "CustomPrefix", etc.
337
337
  """
338
338
  self.auth_header_format = auth_header_format
339
-
339
+
340
340
  def _headers(self) -> dict[str, str]:
341
341
  token = self._get_access_token()
342
342
  return {
@@ -345,7 +345,7 @@ class FlexibleOAuth2Client:
345
345
  }
346
346
 
347
347
  # Usage:
348
- zoho_client = FlexibleOAuth2Client(auth_header_format="Zoho-oauthtoken")
348
+ custom_client = FlexibleOAuth2Client(auth_header_format="CustomPrefix")
349
349
  github_client = FlexibleOAuth2Client(auth_header_format="Token")
350
350
  standard_client = FlexibleOAuth2Client(auth_header_format="Bearer")
351
351
  ```
@@ -227,7 +227,7 @@ class ExternalAPIManager:
227
227
 
228
228
  ## OAuth2-Based External APIs
229
229
 
230
- Many SaaS APIs (Zoho, Okta, Salesforce, Site24x7) use OAuth2 refresh-token flows for long-lived API access without user re-authentication.
230
+ Many SaaS APIs use OAuth2 refresh-token flows for long-lived API access without user re-authentication.
231
231
 
232
232
  ### Pattern: OAuth2 Refresh-Token Client
233
233
 
@@ -302,7 +302,7 @@ class OAuth2ExternalAPIClient:
302
302
  def _headers(self) -> dict[str, str]:
303
303
  """Get HTTP headers for authenticated requests."""
304
304
  token = self._get_access_token()
305
- # Note: Some APIs use custom headers (e.g. Zoho uses "Zoho-oauthtoken")
305
+ # Note: Some APIs use custom headers
306
306
  # See api-security-patterns.md for custom header patterns
307
307
  return {
308
308
  "Authorization": f"Bearer {token}", # Standard OAuth2
@@ -337,54 +337,29 @@ class OAuth2ExternalAPIClient:
337
337
  return resp.json()
338
338
  ```
339
339
 
340
- ### Examples
340
+ ### Example Usage
341
341
 
342
- #### Zoho/Site24x7
343
342
  ```python
343
+ # Configure with your API's specific endpoints
344
344
  client = OAuth2ExternalAPIClient(
345
- client_id=os.environ["SITE24X7_CLIENT_ID"],
346
- client_secret=os.environ["SITE24X7_CLIENT_SECRET"],
347
- refresh_token=os.environ["SITE24X7_REFRESH_TOKEN"],
348
- api_base_url="https://www.site24x7.com/api",
349
- token_url="https://accounts.zoho.com/oauth/v2/token",
345
+ client_id=os.environ["API_CLIENT_ID"],
346
+ client_secret=os.environ["API_CLIENT_SECRET"],
347
+ refresh_token=os.environ["API_REFRESH_TOKEN"],
348
+ api_base_url="https://api.example.com/v1",
349
+ token_url="https://api.example.com/oauth/v2/token",
350
350
  )
351
351
 
352
- # Note: Zoho uses custom header "Zoho-oauthtoken" instead of "Bearer"
352
+ # For APIs with custom header formats:
353
353
  # Override _headers() method for custom header format:
354
354
  def _headers(self) -> dict[str, str]:
355
355
  token = self._get_access_token()
356
356
  return {
357
- "Authorization": f"Zoho-oauthtoken {token}", # Custom header
357
+ "Authorization": f"CustomPrefix {token}", # Custom header format
358
358
  "Accept": "application/json",
359
359
  }
360
360
 
361
- status = client.get("/current_status")
362
- ```
363
-
364
- #### Okta
365
- ```python
366
- client = OAuth2ExternalAPIClient(
367
- client_id=os.environ["OKTA_CLIENT_ID"],
368
- client_secret=os.environ["OKTA_CLIENT_SECRET"],
369
- refresh_token=os.environ["OKTA_REFRESH_TOKEN"],
370
- api_base_url=f"https://{org}.okta.com/api/v1",
371
- token_url=f"https://{org}.okta.com/oauth2/v1/token",
372
- )
373
-
374
- users = client.get("/users")
375
- ```
376
-
377
- #### Salesforce
378
- ```python
379
- client = OAuth2ExternalAPIClient(
380
- client_id=os.environ["SALESFORCE_CLIENT_ID"],
381
- client_secret=os.environ["SALESFORCE_CLIENT_SECRET"],
382
- refresh_token=os.environ["SALESFORCE_REFRESH_TOKEN"],
383
- api_base_url="https://yourinstance.salesforce.com/services/data/v58.0",
384
- token_url="https://login.salesforce.com/services/oauth2/token",
385
- )
386
-
387
- accounts = client.get("/sobjects/Account")
361
+ # Make authenticated requests
362
+ data = client.get("/resource")
388
363
  ```
389
364
 
390
365
  ### Best Practices for OAuth2 External APIs
@@ -413,15 +388,15 @@ accounts = client.get("/sobjects/Account")
413
388
  # Some providers have different endpoints for different regions
414
389
  REGIONS = {
415
390
  "us": {
416
- "api_base_url": "https://www.site24x7.com/api",
417
- "token_url": "https://accounts.zoho.com/oauth/v2/token",
391
+ "api_base_url": "https://api.example.com/v1",
392
+ "token_url": "https://auth.example.com/oauth/v2/token",
418
393
  },
419
394
  "eu": {
420
- "api_base_url": "https://www.site24x7.eu/api",
421
- "token_url": "https://accounts.zoho.eu/oauth/v2/token",
395
+ "api_base_url": "https://api.example.eu/v1",
396
+ "token_url": "https://auth.example.eu/oauth/v2/token",
422
397
  },
423
398
  }
424
-
399
+
425
400
  client = OAuth2ExternalAPIClient(
426
401
  ...,
427
402
  **REGIONS["eu"], # Use EU endpoints
@@ -429,7 +404,7 @@ accounts = client.get("/sobjects/Account")
429
404
  ```
430
405
 
431
406
  4. **Custom Header Formats:**
432
- - Some APIs (Zoho, GitHub) use non-standard auth headers
407
+ - Some APIs use non-standard auth headers (e.g., GitHub's "Token" format)
433
408
  - Make header format configurable or override `_headers()` method
434
409
  - See `api-security-patterns.md` section "Custom Authentication Headers" for details
435
410
 
@@ -0,0 +1,324 @@
1
+ """
2
+ Outcome Health Check.
3
+
4
+ Checks quality trends and improvement metrics.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from datetime import datetime, timedelta
11
+ from pathlib import Path
12
+
13
+ from ...workflow.analytics_dashboard_cursor import CursorAnalyticsAccessor
14
+ from ...workflow.review_artifact import ReviewArtifact
15
+ from ..base import HealthCheck, HealthCheckResult
16
+
17
+
18
+ class OutcomeHealthCheck(HealthCheck):
19
+ """Health check for quality trends and outcomes."""
20
+
21
+ def __init__(self, project_root: Path | None = None, reports_dir: Path | None = None):
22
+ """
23
+ Initialize outcome health check.
24
+
25
+ Args:
26
+ project_root: Project root directory
27
+ reports_dir: Reports directory (defaults to .tapps-agents/reports)
28
+ """
29
+ super().__init__(name="outcomes", dependencies=["environment", "execution"])
30
+ self.project_root = project_root or Path.cwd()
31
+ self.reports_dir = reports_dir or (self.project_root / ".tapps-agents" / "reports")
32
+ self.accessor = CursorAnalyticsAccessor()
33
+
34
+ def _compute_outcomes_from_execution_metrics(self, days: int = 30) -> dict:
35
+ """
36
+ Compute outcomes from execution metrics when review artifacts don't exist.
37
+
38
+ Args:
39
+ days: Number of days to look back for metrics
40
+
41
+ Returns:
42
+ Dictionary with review_executions_count, success_rate, and gate_pass_rate
43
+ """
44
+ try:
45
+ from datetime import UTC
46
+ from ...workflow.execution_metrics import ExecutionMetricsCollector
47
+ import logging
48
+
49
+ collector = ExecutionMetricsCollector(project_root=self.project_root)
50
+
51
+ # Get metrics with reasonable limit (5000 max for ~30 days of heavy usage)
52
+ MAX_METRICS_TO_SCAN = 5000
53
+ all_metrics = collector.get_metrics(limit=MAX_METRICS_TO_SCAN)
54
+
55
+ # Log warning if we hit the limit
56
+ if len(all_metrics) >= MAX_METRICS_TO_SCAN:
57
+ logging.getLogger(__name__).warning(
58
+ "Hit metrics scan limit (%d); results may be incomplete",
59
+ MAX_METRICS_TO_SCAN
60
+ )
61
+
62
+ # Filter for review executions within the last N days (timezone-aware)
63
+ cutoff_date = datetime.now(UTC) - timedelta(days=days)
64
+ review_metrics = []
65
+ for m in all_metrics:
66
+ # Parse timestamp and ensure timezone-aware comparison
67
+ try:
68
+ ts = datetime.fromisoformat(m.started_at.replace("Z", "+00:00"))
69
+ # Convert naive datetime to UTC if needed
70
+ if ts.tzinfo is None:
71
+ from datetime import UTC
72
+ ts = ts.replace(tzinfo=UTC)
73
+
74
+ if ts >= cutoff_date:
75
+ if m.command == "review" or (m.skill and "reviewer" in (m.skill or "").lower()):
76
+ review_metrics.append(m)
77
+ except (ValueError, AttributeError):
78
+ # Skip metrics with invalid timestamps
79
+ continue
80
+
81
+ if not review_metrics:
82
+ return {
83
+ "review_executions_count": 0,
84
+ "success_rate": 0.0,
85
+ "gate_pass_rate": None,
86
+ }
87
+
88
+ total = len(review_metrics)
89
+ success_count = sum(1 for m in review_metrics if m.status == "success")
90
+ success_rate = (success_count / total * 100) if total > 0 else 0.0
91
+
92
+ # Calculate gate pass rate (only for metrics that have gate_pass field)
93
+ gate_pass_metrics = [m for m in review_metrics if m.gate_pass is not None]
94
+ if gate_pass_metrics:
95
+ gate_pass_count = sum(1 for m in gate_pass_metrics if m.gate_pass is True)
96
+ gate_pass_rate = (gate_pass_count / len(gate_pass_metrics) * 100)
97
+ else:
98
+ gate_pass_rate = None
99
+
100
+ return {
101
+ "review_executions_count": total,
102
+ "success_rate": success_rate,
103
+ "gate_pass_rate": gate_pass_rate,
104
+ }
105
+
106
+ except Exception as e:
107
+ # If fallback fails, log and return empty result
108
+ import logging
109
+ logging.getLogger(__name__).debug(
110
+ "Failed to compute outcomes from execution metrics: %s", e
111
+ )
112
+ return {
113
+ "review_executions_count": 0,
114
+ "success_rate": 0.0,
115
+ "gate_pass_rate": None,
116
+ }
117
+
118
+ def run(self) -> HealthCheckResult:
119
+ """
120
+ Run outcome health check.
121
+
122
+ Returns:
123
+ HealthCheckResult with outcome trends
124
+ """
125
+ try:
126
+ # Get analytics data for trends
127
+ dashboard_data = self.accessor.get_dashboard_data()
128
+ agents_data = dashboard_data.get("agents", [])
129
+ workflows_data = dashboard_data.get("workflows", [])
130
+
131
+ # Look for review artifacts in reports directory
132
+ review_artifacts = []
133
+ if self.reports_dir.exists():
134
+ for artifact_file in self.reports_dir.rglob("review_*.json"):
135
+ try:
136
+ with open(artifact_file, encoding="utf-8") as f:
137
+ data = json.load(f)
138
+ artifact = ReviewArtifact.from_dict(data)
139
+ if artifact.overall_score is not None:
140
+ review_artifacts.append(artifact)
141
+ except Exception:
142
+ continue
143
+
144
+ # Calculate trends from review artifacts
145
+ score_trend = "unknown"
146
+ avg_score = 0.0
147
+ score_change = 0.0
148
+
149
+ if review_artifacts:
150
+ # Sort by timestamp
151
+ review_artifacts.sort(key=lambda a: a.timestamp)
152
+
153
+ # Get recent artifacts (last 30 days)
154
+ thirty_days_ago = datetime.now() - timedelta(days=30)
155
+ recent_artifacts = [
156
+ a
157
+ for a in review_artifacts
158
+ if datetime.fromisoformat(a.timestamp.replace("Z", "+00:00")) >= thirty_days_ago
159
+ ]
160
+
161
+ if recent_artifacts:
162
+ scores = [a.overall_score for a in recent_artifacts if a.overall_score is not None]
163
+ if scores:
164
+ avg_score = sum(scores) / len(scores)
165
+
166
+ # Calculate trend (compare first half to second half)
167
+ if len(scores) >= 4:
168
+ first_half = scores[: len(scores) // 2]
169
+ second_half = scores[len(scores) // 2 :]
170
+ first_avg = sum(first_half) / len(first_half)
171
+ second_avg = sum(second_half) / len(second_half)
172
+ score_change = second_avg - first_avg
173
+
174
+ if score_change > 5.0:
175
+ score_trend = "improving"
176
+ elif score_change < -5.0:
177
+ score_trend = "degrading"
178
+ else:
179
+ score_trend = "stable"
180
+
181
+ # Count quality improvement workflows
182
+ quality_workflows = [
183
+ w
184
+ for w in workflows_data
185
+ if "quality" in w.get("workflow_name", "").lower()
186
+ or "improve" in w.get("workflow_name", "").lower()
187
+ ]
188
+ improvement_cycles = len(quality_workflows)
189
+
190
+ # Calculate health score
191
+ score = 100.0
192
+ issues = []
193
+ remediation = []
194
+
195
+ # Check if we have any data; if not, try fallback to execution metrics (review steps)
196
+ if not review_artifacts and not agents_data:
197
+ # Fallback: derive outcomes from execution metrics (review steps, gate_pass)
198
+ import logging
199
+ fallback_data = self._compute_outcomes_from_execution_metrics(days=30)
200
+
201
+ if fallback_data["review_executions_count"] > 0:
202
+ total = fallback_data["review_executions_count"]
203
+ success_rate = fallback_data["success_rate"]
204
+ gate_pass_rate = fallback_data["gate_pass_rate"]
205
+
206
+ # Calculate score: 60 base + 10 if success_rate ≥80% + 5 if gate_pass_rate ≥70%
207
+ fallback_score = 60.0
208
+ if success_rate >= 80.0:
209
+ fallback_score += 10.0
210
+ if gate_pass_rate is not None and gate_pass_rate >= 70.0:
211
+ fallback_score += 5.0
212
+
213
+ # Build message
214
+ gate_msg = f"{gate_pass_rate:.0f}% passed gate" if gate_pass_rate is not None else "no gate data"
215
+ message = (
216
+ f"Outcomes derived from execution metrics: {total} review steps, "
217
+ f"{gate_msg}"
218
+ )
219
+
220
+ logging.getLogger(__name__).info(
221
+ "Outcomes fallback activated: %d review executions processed", total
222
+ )
223
+
224
+ return HealthCheckResult(
225
+ name=self.name,
226
+ status="degraded",
227
+ score=fallback_score,
228
+ message=message,
229
+ details={
230
+ "average_score": 0.0,
231
+ "score_trend": "unknown",
232
+ "score_change": 0.0,
233
+ "review_artifacts_count": 0,
234
+ "improvement_cycles": 0,
235
+ "reports_dir": str(self.reports_dir),
236
+ "fallback_used": True,
237
+ "fallback_source": "execution_metrics",
238
+ "review_executions_count": total,
239
+ "success_rate": success_rate,
240
+ "gate_pass_rate": gate_pass_rate,
241
+ "issues": [],
242
+ },
243
+ remediation=[
244
+ "Run reviewer agent or quality workflows to generate review artifacts"
245
+ ],
246
+ )
247
+
248
+ score = 50.0
249
+ issues.append("No quality metrics available")
250
+ remediation.append("Run reviewer agent or quality workflows to generate metrics")
251
+ else:
252
+ # Check score trend
253
+ if score_trend == "degrading":
254
+ score -= 20.0
255
+ issues.append(f"Quality scores declining: {score_change:.1f} point change")
256
+ remediation.append("Investigate recent code changes causing quality decline")
257
+ elif score_trend == "improving":
258
+ # Bonus for improvement
259
+ score = min(100.0, score + 5.0)
260
+
261
+ # Check average score
262
+ if avg_score > 0:
263
+ if avg_score < 60.0:
264
+ score -= 30.0
265
+ issues.append(f"Low average quality score: {avg_score:.1f}/100")
266
+ remediation.append("Run quality improvement workflows")
267
+ elif avg_score < 75.0:
268
+ score -= 15.0
269
+ issues.append(f"Moderate quality score: {avg_score:.1f}/100")
270
+
271
+ # Check improvement activity
272
+ if improvement_cycles == 0:
273
+ score -= 10.0
274
+ issues.append("No quality improvement workflows run")
275
+ remediation.append("Run quality workflows to improve code quality")
276
+
277
+ # Determine status
278
+ if score >= 85.0:
279
+ status = "healthy"
280
+ elif score >= 70.0:
281
+ status = "degraded"
282
+ else:
283
+ status = "unhealthy"
284
+
285
+ # Build message
286
+ message_parts = []
287
+ if avg_score > 0:
288
+ message_parts.append(f"Avg score: {avg_score:.1f}")
289
+ if score_trend != "unknown":
290
+ message_parts.append(f"Trend: {score_trend}")
291
+ if improvement_cycles > 0:
292
+ message_parts.append(f"Improvements: {improvement_cycles}")
293
+ if not message_parts:
294
+ message = "No outcome data available"
295
+ else:
296
+ message = " | ".join(message_parts)
297
+
298
+ return HealthCheckResult(
299
+ name=self.name,
300
+ status=status,
301
+ score=max(0.0, score),
302
+ message=message,
303
+ details={
304
+ "average_score": avg_score,
305
+ "score_trend": score_trend,
306
+ "score_change": score_change,
307
+ "review_artifacts_count": len(review_artifacts),
308
+ "improvement_cycles": improvement_cycles,
309
+ "reports_dir": str(self.reports_dir),
310
+ "issues": issues,
311
+ },
312
+ remediation=remediation if remediation else None,
313
+ )
314
+
315
+ except Exception as e:
316
+ return HealthCheckResult(
317
+ name=self.name,
318
+ status="unhealthy",
319
+ score=0.0,
320
+ message=f"Outcome check failed: {e}",
321
+ details={"error": str(e), "reports_dir": str(self.reports_dir)},
322
+ remediation=["Check reports directory and analytics access"],
323
+ )
324
+