devtorch-core 3.0.1__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 (193) hide show
  1. devtorch_core/__init__.py +158 -0
  2. devtorch_core/aggphi_textual.py +275 -0
  3. devtorch_core/alerts/__init__.py +23 -0
  4. devtorch_core/alerts/base.py +46 -0
  5. devtorch_core/alerts/config.py +60 -0
  6. devtorch_core/alerts/dispatcher.py +110 -0
  7. devtorch_core/alerts/jira.py +96 -0
  8. devtorch_core/alerts/linear.py +72 -0
  9. devtorch_core/alerts/pagerduty.py +66 -0
  10. devtorch_core/alerts/slack.py +81 -0
  11. devtorch_core/alerts/teams.py +70 -0
  12. devtorch_core/audit/__init__.py +43 -0
  13. devtorch_core/audit/exporter.py +297 -0
  14. devtorch_core/audit/privacy.py +101 -0
  15. devtorch_core/audit/scrubber.py +149 -0
  16. devtorch_core/audit/service.py +67 -0
  17. devtorch_core/audit/signing.py +127 -0
  18. devtorch_core/broadcast/__init__.py +4 -0
  19. devtorch_core/broadcast/broadcaster.py +100 -0
  20. devtorch_core/broadcast/watcher.py +71 -0
  21. devtorch_core/capability.py +639 -0
  22. devtorch_core/cloud/__init__.py +1 -0
  23. devtorch_core/cloud/client_config.py +472 -0
  24. devtorch_core/cloud/client_configs/.claude-opencode-fallback.json +8 -0
  25. devtorch_core/cloud/client_configs/.claude-stdio.json +13 -0
  26. devtorch_core/cloud/client_configs/.cursor-mcp.json +13 -0
  27. devtorch_core/cloud/client_configs/.opencode-bridge.json +13 -0
  28. devtorch_core/cloud/client_configs/.opencode.json +15 -0
  29. devtorch_core/cloud/client_configs/.vscode-mcp.json +13 -0
  30. devtorch_core/cloud/devtorch-mcp-bridge.js +357 -0
  31. devtorch_core/cloud/mcp_client.py +229 -0
  32. devtorch_core/cloud/setup.py +144 -0
  33. devtorch_core/cloud/sync.py +143 -0
  34. devtorch_core/cloud/sync_bundle.py +603 -0
  35. devtorch_core/cloud/sync_conflicts.py +159 -0
  36. devtorch_core/cloud/sync_state.py +159 -0
  37. devtorch_core/cloud/team_sync.py +283 -0
  38. devtorch_core/codex/__init__.py +9 -0
  39. devtorch_core/codex/__main__.py +97 -0
  40. devtorch_core/codex/capture.py +208 -0
  41. devtorch_core/codex/proxy.py +412 -0
  42. devtorch_core/concept_catalog.py +209 -0
  43. devtorch_core/consolidation/__init__.py +3 -0
  44. devtorch_core/consolidation/synthesizer.py +87 -0
  45. devtorch_core/consolidation/workflow.py +175 -0
  46. devtorch_core/daemon/__init__.py +27 -0
  47. devtorch_core/daemon/supervisor.py +293 -0
  48. devtorch_core/daemon/watcher.py +244 -0
  49. devtorch_core/dashboard_api.py +2012 -0
  50. devtorch_core/deltaf.py +97 -0
  51. devtorch_core/disclosure.py +50 -0
  52. devtorch_core/divergence/__init__.py +3 -0
  53. devtorch_core/divergence/detector.py +166 -0
  54. devtorch_core/gateway/__init__.py +32 -0
  55. devtorch_core/gateway/key_manager.py +124 -0
  56. devtorch_core/gateway/metrics_webhook.py +252 -0
  57. devtorch_core/gateway/policy.py +262 -0
  58. devtorch_core/gateway/server.py +727 -0
  59. devtorch_core/gateway/sso.py +233 -0
  60. devtorch_core/gcc.py +1246 -0
  61. devtorch_core/github/__init__.py +35 -0
  62. devtorch_core/github/app.py +240 -0
  63. devtorch_core/github/comment_builder.py +113 -0
  64. devtorch_core/github/pat.py +76 -0
  65. devtorch_core/github/pr_parser.py +82 -0
  66. devtorch_core/github/pr_reporter.py +555 -0
  67. devtorch_core/gitlab/__init__.py +177 -0
  68. devtorch_core/hitl/__init__.py +4 -0
  69. devtorch_core/hitl/channels.py +129 -0
  70. devtorch_core/hitl/orchestrator.py +95 -0
  71. devtorch_core/hooks/__init__.py +17 -0
  72. devtorch_core/hooks/claude_code.py +228 -0
  73. devtorch_core/hooks/git_capture.py +341 -0
  74. devtorch_core/hooks/git_commit.py +182 -0
  75. devtorch_core/hooks/installer.py +733 -0
  76. devtorch_core/hooks/pre_commit.py +157 -0
  77. devtorch_core/hooks/runner.py +344 -0
  78. devtorch_core/identity/__init__.py +4 -0
  79. devtorch_core/identity/agent.py +86 -0
  80. devtorch_core/identity/providers.py +85 -0
  81. devtorch_core/invariants.py +182 -0
  82. devtorch_core/mcp/__init__.py +10 -0
  83. devtorch_core/mcp/auth.py +177 -0
  84. devtorch_core/mcp/server.py +1049 -0
  85. devtorch_core/metrics/__init__.py +35 -0
  86. devtorch_core/metrics/aggregate.py +215 -0
  87. devtorch_core/metrics/calibrate.py +198 -0
  88. devtorch_core/metrics/calibration.py +125 -0
  89. devtorch_core/metrics/credibility.py +288 -0
  90. devtorch_core/metrics/delivery_time.py +70 -0
  91. devtorch_core/metrics/dhs.py +126 -0
  92. devtorch_core/metrics/mcs.py +96 -0
  93. devtorch_core/metrics/roi.py +88 -0
  94. devtorch_core/metrics/session_writer.py +81 -0
  95. devtorch_core/metrics/shadow_ai.py +117 -0
  96. devtorch_core/metrics/sprint_writer.py +243 -0
  97. devtorch_core/observability/__init__.py +78 -0
  98. devtorch_core/observability/datadog.py +157 -0
  99. devtorch_core/observability/formatter.py +119 -0
  100. devtorch_core/observability/report.py +264 -0
  101. devtorch_core/observability/servicenow.py +147 -0
  102. devtorch_core/observability/splunk.py +218 -0
  103. devtorch_core/observability/webhook.py +227 -0
  104. devtorch_core/parser/__init__.py +30 -0
  105. devtorch_core/parser/blocks.py +216 -0
  106. devtorch_core/parser/inference.py +159 -0
  107. devtorch_core/parser/thinking.py +112 -0
  108. devtorch_core/projects.py +169 -0
  109. devtorch_core/prompt_artifact.py +76 -0
  110. devtorch_core/proxy/__init__.py +9 -0
  111. devtorch_core/proxy/routes/__init__.py +1 -0
  112. devtorch_core/proxy/routes/anthropic.py +264 -0
  113. devtorch_core/proxy/routes/azure_openai.py +336 -0
  114. devtorch_core/proxy/routes/gemini.py +331 -0
  115. devtorch_core/proxy/routes/groq.py +284 -0
  116. devtorch_core/proxy/routes/ollama.py +279 -0
  117. devtorch_core/proxy/routes/openai.py +287 -0
  118. devtorch_core/proxy/server.py +356 -0
  119. devtorch_core/query/__init__.py +15 -0
  120. devtorch_core/query/grep.py +181 -0
  121. devtorch_core/query/hybrid.py +86 -0
  122. devtorch_core/query/semantic.py +157 -0
  123. devtorch_core/rdp.py +105 -0
  124. devtorch_core/reasoning/__init__.py +4 -0
  125. devtorch_core/reasoning/entry.py +31 -0
  126. devtorch_core/reasoning/store.py +122 -0
  127. devtorch_core/reasoning_plus/__init__.py +70 -0
  128. devtorch_core/reasoning_plus/augmenter.py +326 -0
  129. devtorch_core/reasoning_plus/capture.py +51 -0
  130. devtorch_core/reasoning_plus/config.py +256 -0
  131. devtorch_core/reasoning_plus/context.py +262 -0
  132. devtorch_core/reasoning_plus/learning/__init__.py +72 -0
  133. devtorch_core/reasoning_plus/learning/analytics.py +141 -0
  134. devtorch_core/reasoning_plus/learning/api.py +313 -0
  135. devtorch_core/reasoning_plus/learning/chain.py +285 -0
  136. devtorch_core/reasoning_plus/learning/composer.py +74 -0
  137. devtorch_core/reasoning_plus/learning/cross_project.py +234 -0
  138. devtorch_core/reasoning_plus/learning/embeddings.py +209 -0
  139. devtorch_core/reasoning_plus/learning/extractor.py +207 -0
  140. devtorch_core/reasoning_plus/learning/models.py +116 -0
  141. devtorch_core/reasoning_plus/learning/provenance.py +126 -0
  142. devtorch_core/reasoning_plus/learning/recorder.py +81 -0
  143. devtorch_core/reasoning_plus/learning/relevance.py +122 -0
  144. devtorch_core/reasoning_plus/learning/state.py +86 -0
  145. devtorch_core/reasoning_plus/learning/store.py +160 -0
  146. devtorch_core/reasoning_plus/learning/theta_learning_bridge.py +94 -0
  147. devtorch_core/reasoning_plus/prompt.py +90 -0
  148. devtorch_core/rep.py +134 -0
  149. devtorch_core/rep_network/__init__.py +25 -0
  150. devtorch_core/rep_network/merge.py +70 -0
  151. devtorch_core/rep_network/node.py +137 -0
  152. devtorch_core/rep_network/server.py +140 -0
  153. devtorch_core/rep_network/sync.py +207 -0
  154. devtorch_core/sensitivity.py +182 -0
  155. devtorch_core/serve.py +258 -0
  156. devtorch_core/session/__init__.py +39 -0
  157. devtorch_core/session/disagreement.py +188 -0
  158. devtorch_core/session/models.py +114 -0
  159. devtorch_core/session/orchestrator.py +182 -0
  160. devtorch_core/session/planner.py +169 -0
  161. devtorch_core/session/simulator.py +132 -0
  162. devtorch_core/signing.py +290 -0
  163. devtorch_core/sis.py +197 -0
  164. devtorch_core/storage.py +308 -0
  165. devtorch_core/templates/__init__.py +6 -0
  166. devtorch_core/templates/engine.py +122 -0
  167. devtorch_core/templates/go.py +18 -0
  168. devtorch_core/templates/infra.py +19 -0
  169. devtorch_core/templates/library/__init__.py +18 -0
  170. devtorch_core/templates/library/api_design.md +27 -0
  171. devtorch_core/templates/library/bug_fix.md +27 -0
  172. devtorch_core/templates/library/decision_record.md +27 -0
  173. devtorch_core/templates/library/engine.py +228 -0
  174. devtorch_core/templates/library/security_review.md +30 -0
  175. devtorch_core/templates/python.py +19 -0
  176. devtorch_core/templates/react.py +18 -0
  177. devtorch_core/templates/typescript.py +18 -0
  178. devtorch_core/theta.py +221 -0
  179. devtorch_core/theta_synthesis.py +268 -0
  180. devtorch_core/topics.py +320 -0
  181. devtorch_core/variance.py +219 -0
  182. devtorch_core/wrapper/__init__.py +52 -0
  183. devtorch_core/wrapper/anthropic.py +487 -0
  184. devtorch_core/wrapper/base.py +562 -0
  185. devtorch_core/wrapper/bedrock.py +342 -0
  186. devtorch_core/wrapper/gemini.py +422 -0
  187. devtorch_core/wrapper/ollama.py +527 -0
  188. devtorch_core/wrapper/openai.py +461 -0
  189. devtorch_core-3.0.1.dist-info/METADATA +867 -0
  190. devtorch_core-3.0.1.dist-info/RECORD +193 -0
  191. devtorch_core-3.0.1.dist-info/WHEEL +5 -0
  192. devtorch_core-3.0.1.dist-info/entry_points.txt +2 -0
  193. devtorch_core-3.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,288 @@
1
+ """
2
+ Metric credibility infrastructure.
3
+
4
+ Every computed metric in DevTorch is tagged with:
5
+ - TIER: MEASURED | CALIBRATED | UNAVAILABLE
6
+ - CITATIONS: references to the data sources behind any assumptions
7
+ - DISCLOSURE: explicit statement of design choices vs. validated benchmarks
8
+
9
+ MEASURED — derived exactly from .GCC/ events with no external assumptions.
10
+ Example: commit count, sensitivity event count, branch count.
11
+
12
+ CALIBRATED — requires at least one team-specific input to be meaningful.
13
+ The formula is exact but one input must be provided by the team.
14
+ Example: DHS (formula exact, weights are design choices),
15
+ ROI cost (exact formula, team must set developer_hourly_rate).
16
+
17
+ UNAVAILABLE — requires longitudinal data or external integration not yet built.
18
+ Returns None and explains what's needed.
19
+ Example: per-collision rework time (no peer-reviewed benchmark exists).
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from dataclasses import asdict, dataclass, field
25
+ from enum import Enum
26
+ from typing import Any, Optional
27
+
28
+
29
+ # ---------------------------------------------------------------------------
30
+ # Tier enum
31
+ # ---------------------------------------------------------------------------
32
+
33
+
34
+ class MetricTier(str, Enum):
35
+ """Credibility tier assigned to each DevTorch metric."""
36
+
37
+ MEASURED = "measured"
38
+ CALIBRATED = "calibrated"
39
+ UNAVAILABLE = "unavailable"
40
+
41
+
42
+ # ---------------------------------------------------------------------------
43
+ # Dataclasses
44
+ # ---------------------------------------------------------------------------
45
+
46
+
47
+ @dataclass
48
+ class Citation:
49
+ """A single academic or authoritative reference backing a metric assumption."""
50
+
51
+ title: str
52
+ authors: str
53
+ year: int
54
+ doi_or_url: str
55
+ claim: str # The specific claim being cited (e.g. "15 min task resumption time")
56
+ scope: str # Scope / caveat (e.g. "programming tasks only, not general work")
57
+
58
+
59
+ @dataclass
60
+ class DesignChoice:
61
+ """A parameter that is a deliberate design decision, not an external benchmark."""
62
+
63
+ name: str
64
+ value: Any # The current default value
65
+ description: str # Why this value was chosen
66
+ calibration_instruction: str # How the team should calibrate it
67
+
68
+
69
+ @dataclass
70
+ class MetricMetadata:
71
+ """Full credibility metadata for one named metric."""
72
+
73
+ metric_name: str
74
+ tier: MetricTier
75
+ citations: list[Citation] = field(default_factory=list)
76
+ design_choices: list[DesignChoice] = field(default_factory=list)
77
+ unavailable_reason: str = "" # Why it is unavailable (if tier == UNAVAILABLE)
78
+ calibration_required: list[str] = field(
79
+ default_factory=list
80
+ ) # Field names the team must set
81
+
82
+
83
+ # ---------------------------------------------------------------------------
84
+ # Metric registry
85
+ # ---------------------------------------------------------------------------
86
+
87
+ METRIC_REGISTRY: dict[str, MetricMetadata] = {
88
+ "commit_count": MetricMetadata(
89
+ metric_name="commit_count",
90
+ tier=MetricTier.MEASURED,
91
+ citations=[],
92
+ design_choices=[],
93
+ ),
94
+ "sensitivity_event_count": MetricMetadata(
95
+ metric_name="sensitivity_event_count",
96
+ tier=MetricTier.MEASURED,
97
+ ),
98
+ "high_disclosure_count": MetricMetadata(
99
+ metric_name="high_disclosure_count",
100
+ tier=MetricTier.MEASURED,
101
+ ),
102
+ "context_switch_minutes": MetricMetadata(
103
+ metric_name="context_switch_minutes",
104
+ tier=MetricTier.CALIBRATED,
105
+ citations=[
106
+ Citation(
107
+ title="Software exploratory works in the context of sustained attention",
108
+ authors="Parnin, C. & Rugaber, S.",
109
+ year=2011,
110
+ doi_or_url="https://doi.org/10.1007/s11219-010-9104-9",
111
+ claim="15 minutes average to resume a programming task after interruption",
112
+ scope="Programming tasks only. Not validated for general knowledge work or AI agent context switches.",
113
+ )
114
+ ],
115
+ design_choices=[
116
+ DesignChoice(
117
+ name="context_switch_minutes",
118
+ value=15.0,
119
+ description="Default from Parnin & Rugaber (2011). Measured for programming tasks.",
120
+ calibration_instruction="Time your team's actual context switch cost with a simple stopwatch study over 1 week.",
121
+ )
122
+ ],
123
+ calibration_required=[],
124
+ ),
125
+ "developer_hourly_rate": MetricMetadata(
126
+ metric_name="developer_hourly_rate",
127
+ tier=MetricTier.CALIBRATED,
128
+ citations=[],
129
+ design_choices=[
130
+ DesignChoice(
131
+ name="developer_hourly_rate",
132
+ value=0.0,
133
+ description="No universal default. Teams must set this explicitly. BLS SOC 15-1252 (2023) gives $55-65/hr median for software developers.",
134
+ calibration_instruction="Use your team's actual loaded cost (salary + benefits + overhead). Set via devtorch calibrate --set developer_hourly_rate=<value>.",
135
+ )
136
+ ],
137
+ calibration_required=["developer_hourly_rate"],
138
+ ),
139
+ "rework_minutes_per_collision": MetricMetadata(
140
+ metric_name="rework_minutes_per_collision",
141
+ tier=MetricTier.UNAVAILABLE,
142
+ unavailable_reason="No peer-reviewed benchmark exists for per-collision rework time in AI multi-agent systems. Requires longitudinal team data. Set developer_hourly_rate and track actual rework time in your team's retrospectives.",
143
+ ),
144
+ "mcs_score": MetricMetadata(
145
+ metric_name="mcs_score",
146
+ tier=MetricTier.CALIBRATED,
147
+ citations=[],
148
+ design_choices=[
149
+ DesignChoice(
150
+ name="mcs_weight_frequency",
151
+ value=0.4,
152
+ description="Weight for collision frequency. Design choice — no external benchmark.",
153
+ calibration_instruction="Adjust based on whether frequency or severity matters more for your team.",
154
+ ),
155
+ DesignChoice(
156
+ name="mcs_weight_severity",
157
+ value=0.4,
158
+ description="Weight for collision severity. Design choice — no external benchmark.",
159
+ calibration_instruction="Increase if severe but rare collisions are your main concern.",
160
+ ),
161
+ DesignChoice(
162
+ name="mcs_weight_resolution_time",
163
+ value=0.2,
164
+ description="Weight for resolution time. Design choice — no external benchmark.",
165
+ calibration_instruction="Increase if slow resolution times are your main concern.",
166
+ ),
167
+ ],
168
+ calibration_required=[],
169
+ ),
170
+ "dhs_score": MetricMetadata(
171
+ metric_name="dhs_score",
172
+ tier=MetricTier.CALIBRATED,
173
+ citations=[],
174
+ design_choices=[
175
+ DesignChoice(
176
+ name="dhs_weight_commit_health",
177
+ value=0.3,
178
+ description="Weight for commit-backed decision ratio. Design choice.",
179
+ calibration_instruction="Increase if commit discipline is your primary concern.",
180
+ ),
181
+ DesignChoice(
182
+ name="dhs_weight_sensitivity_health",
183
+ value=0.25,
184
+ description="Weight for sensitivity event health. Design choice.",
185
+ calibration_instruction="Increase for compliance-heavy teams.",
186
+ ),
187
+ DesignChoice(
188
+ name="dhs_weight_capability_health",
189
+ value=0.25,
190
+ description="Weight for A1 capability gate health. Design choice.",
191
+ calibration_instruction="Decrease if your team rarely uses textual mode.",
192
+ ),
193
+ DesignChoice(
194
+ name="dhs_weight_variance_health",
195
+ value=0.2,
196
+ description="Weight for variance alert rate. Design choice.",
197
+ calibration_instruction="Increase if variance stability is critical for your use case.",
198
+ ),
199
+ ],
200
+ calibration_required=[],
201
+ ),
202
+ "auditor_hourly_rate": MetricMetadata(
203
+ metric_name="auditor_hourly_rate",
204
+ tier=MetricTier.CALIBRATED,
205
+ citations=[
206
+ Citation(
207
+ title="Occupational Employment and Wages, Accountants and Auditors",
208
+ authors="U.S. Bureau of Labor Statistics",
209
+ year=2023,
210
+ doi_or_url="https://www.bls.gov/oes/2023/may/oes132011.htm",
211
+ claim="$39-40/hr median wage for employed accountants and auditors (SOC 13-2011)",
212
+ scope="Employed staff auditors only. External billing rates ($75-400/hr) not reflected here.",
213
+ )
214
+ ],
215
+ design_choices=[
216
+ DesignChoice(
217
+ name="auditor_hourly_rate",
218
+ value=0.0,
219
+ description="No universal default. BLS gives $39-40/hr for employed staff auditors (SOC 13-2011). External rates are $75-400/hr.",
220
+ calibration_instruction="Use your actual auditor rate. Set via devtorch calibrate --set auditor_hourly_rate=<value>.",
221
+ )
222
+ ],
223
+ calibration_required=["auditor_hourly_rate"],
224
+ ),
225
+ }
226
+
227
+
228
+ # ---------------------------------------------------------------------------
229
+ # Helper functions
230
+ # ---------------------------------------------------------------------------
231
+
232
+
233
+ def get_metric_metadata(metric_name: str) -> Optional[MetricMetadata]:
234
+ """Return the MetricMetadata for the named metric, or None if not in registry."""
235
+ return METRIC_REGISTRY.get(metric_name)
236
+
237
+
238
+ def list_uncalibrated_metrics(calibration_dict: dict) -> list[str]:
239
+ """
240
+ Given a dict of currently-set calibration values, return metric names that
241
+ require calibration but are missing from calibration_dict.
242
+
243
+ Only checks the ``calibration_required`` field of each MetricMetadata entry.
244
+ """
245
+ uncalibrated: list[str] = []
246
+ for metric_name, meta in METRIC_REGISTRY.items():
247
+ if meta.calibration_required:
248
+ missing = any(
249
+ key not in calibration_dict for key in meta.calibration_required
250
+ )
251
+ if missing:
252
+ uncalibrated.append(metric_name)
253
+ return uncalibrated
254
+
255
+
256
+ def format_citation(citation: Citation) -> str:
257
+ """Return a formatted citation string: '{authors} ({year}). {title}. {doi_or_url}'"""
258
+ return f"{citation.authors} ({citation.year}). {citation.title}. {citation.doi_or_url}"
259
+
260
+
261
+ def metric_metadata_to_dict(meta: MetricMetadata) -> dict:
262
+ """Return a JSON-serializable dict representation of MetricMetadata."""
263
+ return {
264
+ "metric_name": meta.metric_name,
265
+ "tier": meta.tier.value,
266
+ "citations": [
267
+ {
268
+ "title": c.title,
269
+ "authors": c.authors,
270
+ "year": c.year,
271
+ "doi_or_url": c.doi_or_url,
272
+ "claim": c.claim,
273
+ "scope": c.scope,
274
+ }
275
+ for c in meta.citations
276
+ ],
277
+ "design_choices": [
278
+ {
279
+ "name": d.name,
280
+ "value": d.value,
281
+ "description": d.description,
282
+ "calibration_instruction": d.calibration_instruction,
283
+ }
284
+ for d in meta.design_choices
285
+ ],
286
+ "unavailable_reason": meta.unavailable_reason,
287
+ "calibration_required": list(meta.calibration_required),
288
+ }
@@ -0,0 +1,70 @@
1
+ """
2
+ Sprint 25 — Delivery-time baseline metric for complex problems.
3
+
4
+ Estimates the time required to deliver required functionality for a complex
5
+ problem by combining the team-calibrated baseline with observed session signals.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from pathlib import Path
11
+ from typing import Any, Dict, List
12
+
13
+
14
+ def _read_sessions(metrics_dir: Path) -> List[dict]:
15
+ """Read all valid session metric files under metrics_dir/session."""
16
+ session_dir = Path(metrics_dir) / "session"
17
+ sessions: List[dict] = []
18
+ if not session_dir.exists():
19
+ return sessions
20
+ for path in session_dir.glob("*.json"):
21
+ try:
22
+ data = json.loads(path.read_text(encoding="utf-8"))
23
+ if isinstance(data, dict):
24
+ sessions.append(data)
25
+ except (json.JSONDecodeError, OSError):
26
+ continue
27
+ return sessions
28
+
29
+
30
+ def compute_delivery_time_summary(
31
+ metrics_dir: Path,
32
+ calibration: Dict[str, Any],
33
+ ) -> Dict[str, Any]:
34
+ """
35
+ Estimate complex-problem delivery time relative to the team baseline.
36
+
37
+ The baseline is a team-calibrated input (default 240 minutes / 4 hours).
38
+ The current estimate is derived from the number of recorded sessions and a
39
+ complexity proxy based on total token volume, so it remains deterministic and
40
+ does not require external project-management data.
41
+
42
+ Returns:
43
+ {
44
+ "complex_problem_delivery_time_baseline_min": float,
45
+ "complex_problem_delivery_time_current_min": float,
46
+ "delta_vs_baseline_min": float,
47
+ "tier": "[CALIBRATED]",
48
+ "sessions_count": int,
49
+ }
50
+ """
51
+ baseline = float(calibration.get("complex_problem_delivery_time_baseline_min", 240.0))
52
+ sessions = _read_sessions(Path(metrics_dir))
53
+ session_count = len(sessions)
54
+ total_tokens = sum(s.get("tokens_used", 0) for s in sessions)
55
+
56
+ # Complexity proxy: 1.0 baseline, grows with token volume.
57
+ # 100k tokens is treated as one additional complexity unit.
58
+ complexity = 1.0 + (total_tokens / 100_000.0)
59
+
60
+ # Each recorded session adds a small overhead factor (5%) to account for
61
+ # context switching and review overhead.
62
+ current = baseline * complexity * (1.0 + session_count * 0.05)
63
+
64
+ return {
65
+ "complex_problem_delivery_time_baseline_min": round(baseline, 2),
66
+ "complex_problem_delivery_time_current_min": round(current, 2),
67
+ "delta_vs_baseline_min": round(current - baseline, 2),
68
+ "tier": "[CALIBRATED]",
69
+ "sessions_count": session_count,
70
+ }
@@ -0,0 +1,126 @@
1
+ """
2
+ DevTorch Health Score (DHS).
3
+
4
+ DHS measures overall governance health of the repository.
5
+ Range: 0.0 (unhealthy) to 1.0 (healthy).
6
+
7
+ IMPORTANT: DHS weights are design choices, not validated benchmarks.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass
13
+ from typing import Optional, Tuple, TYPE_CHECKING
14
+
15
+ from .calibration import CalibrationStore
16
+
17
+ if TYPE_CHECKING:
18
+ from devtorch_core.gcc import GCCRepository
19
+
20
+
21
+ @dataclass
22
+ class DHSComponents:
23
+ """Component scores feeding into the DHS composite."""
24
+
25
+ commit_health: float # 0–1: fraction of decisions that are commit-backed
26
+ sensitivity_health: float # 0–1: 1 - fraction of high-disclosure events
27
+ capability_health: float # 0–1: 1 if A1 gate passing, else 0
28
+ variance_health: float # 0–1: 1 - variance_alert_rate
29
+
30
+
31
+ def compute_dhs(
32
+ components: DHSComponents,
33
+ calibration: Optional[CalibrationStore] = None,
34
+ ) -> float:
35
+ """
36
+ Compute DHS as a weighted sum of component scores.
37
+
38
+ Result is clamped to [0.0, 1.0].
39
+ """
40
+ if calibration is not None:
41
+ w_commit = float(calibration.get("dhs_weight_commit_health", 0.3))
42
+ w_sens = float(calibration.get("dhs_weight_sensitivity_health", 0.25))
43
+ w_cap = float(calibration.get("dhs_weight_capability_health", 0.25))
44
+ w_var = float(calibration.get("dhs_weight_variance_health", 0.2))
45
+ else:
46
+ w_commit, w_sens, w_cap, w_var = 0.3, 0.25, 0.25, 0.2
47
+
48
+ raw = (
49
+ w_commit * components.commit_health
50
+ + w_sens * components.sensitivity_health
51
+ + w_cap * components.capability_health
52
+ + w_var * components.variance_health
53
+ )
54
+ return max(0.0, min(1.0, raw))
55
+
56
+
57
+ def dhs_to_label(dhs: float) -> str:
58
+ """Return a human-readable health label for a DHS value."""
59
+ if dhs >= 0.8:
60
+ return "HEALTHY"
61
+ if dhs >= 0.6:
62
+ return "FAIR"
63
+ if dhs >= 0.4:
64
+ return "DEGRADED"
65
+ return "CRITICAL"
66
+
67
+
68
+ def compute_dhs_from_gcc(gcc_repo: "GCCRepository") -> Tuple[float, DHSComponents]:
69
+ """
70
+ Derive DHS components directly from a GCCRepository and compute the score.
71
+
72
+ Component derivation:
73
+ - commit_health: fraction of commits that are commit-backed (non-zero
74
+ commit count → 1.0; no commits → 0.0).
75
+ - sensitivity_health: 1 - (high-disclosure event fraction). HIGH and
76
+ CRITICAL disclosure levels are counted as high-disclosure.
77
+ - capability_health: 1.0 if the A1 gate passes, else 0.0.
78
+ - variance_health: 1.0 if no variance alert has been fired (f_max > 0
79
+ or no calibration report exists), else 0.0.
80
+
81
+ Returns (dhs_score, components).
82
+ """
83
+ # --- commit_health ---
84
+ try:
85
+ history = gcc_repo.history()
86
+ commit_health = 1.0 if history else 0.0
87
+ except Exception:
88
+ commit_health = 0.0
89
+
90
+ # --- sensitivity_health ---
91
+ try:
92
+ all_events = gcc_repo.list_sensitivities()
93
+ if all_events:
94
+ high_levels = {"HIGH", "CRITICAL"}
95
+ high_count = sum(
96
+ 1 for e in all_events
97
+ if getattr(e, "disclosure_level", "").upper() in high_levels
98
+ )
99
+ sensitivity_health = 1.0 - (high_count / len(all_events))
100
+ else:
101
+ sensitivity_health = 1.0
102
+ except Exception:
103
+ sensitivity_health = 1.0
104
+
105
+ # --- capability_health ---
106
+ try:
107
+ result = gcc_repo.validate_capability()
108
+ capability_health = 1.0 if result.get("passed", False) else 0.0
109
+ except Exception:
110
+ capability_health = 0.0
111
+
112
+ # --- variance_health ---
113
+ try:
114
+ forced = gcc_repo.variance_deterministic_mode_forced()
115
+ variance_health = 0.0 if forced else 1.0
116
+ except Exception:
117
+ variance_health = 1.0
118
+
119
+ components = DHSComponents(
120
+ commit_health=commit_health,
121
+ sensitivity_health=sensitivity_health,
122
+ capability_health=capability_health,
123
+ variance_health=variance_health,
124
+ )
125
+ dhs_score = compute_dhs(components)
126
+ return dhs_score, components
@@ -0,0 +1,96 @@
1
+ """
2
+ Multi-agent Collision Score (MCS).
3
+
4
+ MCS measures how often agent outputs conflict with each other.
5
+ Range: 0.0 (no collisions) to 1.0 (maximum collision severity).
6
+
7
+ IMPORTANT: MCS weights are design choices, not validated benchmarks.
8
+ Calibrate mcs_weight_* values with your team's historical data.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass, field
14
+ from typing import List, Optional
15
+
16
+ from .calibration import CalibrationStore
17
+
18
+
19
+ @dataclass
20
+ class CollisionEvent:
21
+ """A single multi-agent collision event."""
22
+
23
+ timestamp: str
24
+ branch_a: str
25
+ branch_b: str
26
+ concept: str
27
+ severity: float # 0.0–1.0
28
+ resolution_minutes: float = 0.0
29
+
30
+
31
+ def compute_mcs(
32
+ events: List[CollisionEvent],
33
+ calibration: Optional[CalibrationStore] = None,
34
+ ) -> float:
35
+ """
36
+ Compute MCS from a list of CollisionEvents.
37
+
38
+ Returns 0.0 for an empty list. Result is clamped to [0.0, 1.0].
39
+
40
+ Normalisations:
41
+ - frequency_score: number of events / 10 (capped at 1.0)
42
+ - severity_score: mean severity across events
43
+ - resolution_score: mean resolution minutes / 60 (capped at 1.0)
44
+ """
45
+ if not events:
46
+ return 0.0
47
+
48
+ # Retrieve weights from calibration or use defaults
49
+ if calibration is not None:
50
+ w_freq = float(calibration.get("mcs_weight_frequency", 0.4))
51
+ w_sev = float(calibration.get("mcs_weight_severity", 0.4))
52
+ w_res = float(calibration.get("mcs_weight_resolution_time", 0.2))
53
+ else:
54
+ w_freq, w_sev, w_res = 0.4, 0.4, 0.2
55
+
56
+ n = len(events)
57
+ frequency_score = min(n / 10.0, 1.0)
58
+ severity_score = sum(e.severity for e in events) / n
59
+ resolution_score = min(
60
+ sum(e.resolution_minutes for e in events) / n / 60.0,
61
+ 1.0,
62
+ )
63
+
64
+ raw = w_freq * frequency_score + w_sev * severity_score + w_res * resolution_score
65
+ return max(0.0, min(1.0, raw))
66
+
67
+
68
+ def mcs_to_label(mcs: float) -> str:
69
+ """Return a human-readable severity label for an MCS value."""
70
+ if mcs >= 0.8:
71
+ return "CRITICAL"
72
+ if mcs >= 0.5:
73
+ return "HIGH"
74
+ if mcs >= 0.2:
75
+ return "MODERATE"
76
+ return "LOW"
77
+
78
+
79
+ def estimate_rework_cost(
80
+ events: List[CollisionEvent],
81
+ calibration: CalibrationStore,
82
+ ) -> Optional[float]:
83
+ """
84
+ Estimate total rework cost in dollars.
85
+
86
+ Returns None if developer_hourly_rate is 0 (explicitly uncalibrated).
87
+ Raises ValueError if the rate is 0 and the caller expects a numeric result.
88
+ """
89
+ rate = float(calibration.get("developer_hourly_rate", 0.0))
90
+ if rate == 0.0:
91
+ return None
92
+
93
+ rework_minutes = float(calibration.get("rework_minutes_per_collision", 30.0))
94
+ total_minutes = len(events) * rework_minutes
95
+ total_hours = total_minutes / 60.0
96
+ return total_hours * rate
@@ -0,0 +1,88 @@
1
+ """
2
+ ROI and cost estimates.
3
+
4
+ IMPORTANT: All estimates require calibration with real team data.
5
+ Default values are starting points only — treat as uncalibrated until
6
+ your team sets developer_hourly_rate and validates rework assumptions.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass, field
12
+ from typing import List, Optional
13
+
14
+ from .calibration import CalibrationStore
15
+
16
+
17
+ @dataclass
18
+ class ROIEstimate:
19
+ """Result of an ROI/cost-saving estimate."""
20
+
21
+ time_saved_minutes: float
22
+ cost_saved_dollars: Optional[float] # None when developer_hourly_rate not set
23
+ is_calibrated: bool # False when using unset defaults
24
+ calibration_warnings: List[str] = field(default_factory=list)
25
+
26
+
27
+ def estimate_context_switch_savings(
28
+ avoided_switches: int,
29
+ calibration: CalibrationStore,
30
+ ) -> ROIEstimate:
31
+ """
32
+ Estimate time and cost saved by avoiding context switches.
33
+
34
+ Uses context_switch_minutes and developer_hourly_rate from calibration.
35
+ is_calibrated is False when developer_hourly_rate has not been set (== 0).
36
+ """
37
+ switch_minutes = float(calibration.get("context_switch_minutes", 15.0))
38
+ rate = float(calibration.get("developer_hourly_rate", 0.0))
39
+
40
+ time_saved = avoided_switches * switch_minutes
41
+ is_calibrated = rate > 0.0
42
+
43
+ warnings: List[str] = []
44
+ if not is_calibrated:
45
+ warnings.append("Set developer_hourly_rate for cost estimates")
46
+
47
+ cost_saved: Optional[float] = None
48
+ if is_calibrated:
49
+ cost_saved = (time_saved / 60.0) * rate
50
+
51
+ return ROIEstimate(
52
+ time_saved_minutes=time_saved,
53
+ cost_saved_dollars=cost_saved,
54
+ is_calibrated=is_calibrated,
55
+ calibration_warnings=warnings,
56
+ )
57
+
58
+
59
+ def estimate_rework_savings(
60
+ avoided_collisions: int,
61
+ calibration: CalibrationStore,
62
+ ) -> ROIEstimate:
63
+ """
64
+ Estimate time and cost saved by avoiding rework from collisions.
65
+
66
+ Uses rework_minutes_per_collision and developer_hourly_rate from calibration.
67
+ is_calibrated is False when developer_hourly_rate has not been set (== 0).
68
+ """
69
+ rework_minutes = float(calibration.get("rework_minutes_per_collision", 30.0))
70
+ rate = float(calibration.get("developer_hourly_rate", 0.0))
71
+
72
+ time_saved = avoided_collisions * rework_minutes
73
+ is_calibrated = rate > 0.0
74
+
75
+ warnings: List[str] = []
76
+ if not is_calibrated:
77
+ warnings.append("Set developer_hourly_rate for cost estimates")
78
+
79
+ cost_saved: Optional[float] = None
80
+ if is_calibrated:
81
+ cost_saved = (time_saved / 60.0) * rate
82
+
83
+ return ROIEstimate(
84
+ time_saved_minutes=time_saved,
85
+ cost_saved_dollars=cost_saved,
86
+ is_calibrated=is_calibrated,
87
+ calibration_warnings=warnings,
88
+ )