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.
- devtorch_core/__init__.py +158 -0
- devtorch_core/aggphi_textual.py +275 -0
- devtorch_core/alerts/__init__.py +23 -0
- devtorch_core/alerts/base.py +46 -0
- devtorch_core/alerts/config.py +60 -0
- devtorch_core/alerts/dispatcher.py +110 -0
- devtorch_core/alerts/jira.py +96 -0
- devtorch_core/alerts/linear.py +72 -0
- devtorch_core/alerts/pagerduty.py +66 -0
- devtorch_core/alerts/slack.py +81 -0
- devtorch_core/alerts/teams.py +70 -0
- devtorch_core/audit/__init__.py +43 -0
- devtorch_core/audit/exporter.py +297 -0
- devtorch_core/audit/privacy.py +101 -0
- devtorch_core/audit/scrubber.py +149 -0
- devtorch_core/audit/service.py +67 -0
- devtorch_core/audit/signing.py +127 -0
- devtorch_core/broadcast/__init__.py +4 -0
- devtorch_core/broadcast/broadcaster.py +100 -0
- devtorch_core/broadcast/watcher.py +71 -0
- devtorch_core/capability.py +639 -0
- devtorch_core/cloud/__init__.py +1 -0
- devtorch_core/cloud/client_config.py +472 -0
- devtorch_core/cloud/client_configs/.claude-opencode-fallback.json +8 -0
- devtorch_core/cloud/client_configs/.claude-stdio.json +13 -0
- devtorch_core/cloud/client_configs/.cursor-mcp.json +13 -0
- devtorch_core/cloud/client_configs/.opencode-bridge.json +13 -0
- devtorch_core/cloud/client_configs/.opencode.json +15 -0
- devtorch_core/cloud/client_configs/.vscode-mcp.json +13 -0
- devtorch_core/cloud/devtorch-mcp-bridge.js +357 -0
- devtorch_core/cloud/mcp_client.py +229 -0
- devtorch_core/cloud/setup.py +144 -0
- devtorch_core/cloud/sync.py +143 -0
- devtorch_core/cloud/sync_bundle.py +603 -0
- devtorch_core/cloud/sync_conflicts.py +159 -0
- devtorch_core/cloud/sync_state.py +159 -0
- devtorch_core/cloud/team_sync.py +283 -0
- devtorch_core/codex/__init__.py +9 -0
- devtorch_core/codex/__main__.py +97 -0
- devtorch_core/codex/capture.py +208 -0
- devtorch_core/codex/proxy.py +412 -0
- devtorch_core/concept_catalog.py +209 -0
- devtorch_core/consolidation/__init__.py +3 -0
- devtorch_core/consolidation/synthesizer.py +87 -0
- devtorch_core/consolidation/workflow.py +175 -0
- devtorch_core/daemon/__init__.py +27 -0
- devtorch_core/daemon/supervisor.py +293 -0
- devtorch_core/daemon/watcher.py +244 -0
- devtorch_core/dashboard_api.py +2012 -0
- devtorch_core/deltaf.py +97 -0
- devtorch_core/disclosure.py +50 -0
- devtorch_core/divergence/__init__.py +3 -0
- devtorch_core/divergence/detector.py +166 -0
- devtorch_core/gateway/__init__.py +32 -0
- devtorch_core/gateway/key_manager.py +124 -0
- devtorch_core/gateway/metrics_webhook.py +252 -0
- devtorch_core/gateway/policy.py +262 -0
- devtorch_core/gateway/server.py +727 -0
- devtorch_core/gateway/sso.py +233 -0
- devtorch_core/gcc.py +1246 -0
- devtorch_core/github/__init__.py +35 -0
- devtorch_core/github/app.py +240 -0
- devtorch_core/github/comment_builder.py +113 -0
- devtorch_core/github/pat.py +76 -0
- devtorch_core/github/pr_parser.py +82 -0
- devtorch_core/github/pr_reporter.py +555 -0
- devtorch_core/gitlab/__init__.py +177 -0
- devtorch_core/hitl/__init__.py +4 -0
- devtorch_core/hitl/channels.py +129 -0
- devtorch_core/hitl/orchestrator.py +95 -0
- devtorch_core/hooks/__init__.py +17 -0
- devtorch_core/hooks/claude_code.py +228 -0
- devtorch_core/hooks/git_capture.py +341 -0
- devtorch_core/hooks/git_commit.py +182 -0
- devtorch_core/hooks/installer.py +733 -0
- devtorch_core/hooks/pre_commit.py +157 -0
- devtorch_core/hooks/runner.py +344 -0
- devtorch_core/identity/__init__.py +4 -0
- devtorch_core/identity/agent.py +86 -0
- devtorch_core/identity/providers.py +85 -0
- devtorch_core/invariants.py +182 -0
- devtorch_core/mcp/__init__.py +10 -0
- devtorch_core/mcp/auth.py +177 -0
- devtorch_core/mcp/server.py +1049 -0
- devtorch_core/metrics/__init__.py +35 -0
- devtorch_core/metrics/aggregate.py +215 -0
- devtorch_core/metrics/calibrate.py +198 -0
- devtorch_core/metrics/calibration.py +125 -0
- devtorch_core/metrics/credibility.py +288 -0
- devtorch_core/metrics/delivery_time.py +70 -0
- devtorch_core/metrics/dhs.py +126 -0
- devtorch_core/metrics/mcs.py +96 -0
- devtorch_core/metrics/roi.py +88 -0
- devtorch_core/metrics/session_writer.py +81 -0
- devtorch_core/metrics/shadow_ai.py +117 -0
- devtorch_core/metrics/sprint_writer.py +243 -0
- devtorch_core/observability/__init__.py +78 -0
- devtorch_core/observability/datadog.py +157 -0
- devtorch_core/observability/formatter.py +119 -0
- devtorch_core/observability/report.py +264 -0
- devtorch_core/observability/servicenow.py +147 -0
- devtorch_core/observability/splunk.py +218 -0
- devtorch_core/observability/webhook.py +227 -0
- devtorch_core/parser/__init__.py +30 -0
- devtorch_core/parser/blocks.py +216 -0
- devtorch_core/parser/inference.py +159 -0
- devtorch_core/parser/thinking.py +112 -0
- devtorch_core/projects.py +169 -0
- devtorch_core/prompt_artifact.py +76 -0
- devtorch_core/proxy/__init__.py +9 -0
- devtorch_core/proxy/routes/__init__.py +1 -0
- devtorch_core/proxy/routes/anthropic.py +264 -0
- devtorch_core/proxy/routes/azure_openai.py +336 -0
- devtorch_core/proxy/routes/gemini.py +331 -0
- devtorch_core/proxy/routes/groq.py +284 -0
- devtorch_core/proxy/routes/ollama.py +279 -0
- devtorch_core/proxy/routes/openai.py +287 -0
- devtorch_core/proxy/server.py +356 -0
- devtorch_core/query/__init__.py +15 -0
- devtorch_core/query/grep.py +181 -0
- devtorch_core/query/hybrid.py +86 -0
- devtorch_core/query/semantic.py +157 -0
- devtorch_core/rdp.py +105 -0
- devtorch_core/reasoning/__init__.py +4 -0
- devtorch_core/reasoning/entry.py +31 -0
- devtorch_core/reasoning/store.py +122 -0
- devtorch_core/reasoning_plus/__init__.py +70 -0
- devtorch_core/reasoning_plus/augmenter.py +326 -0
- devtorch_core/reasoning_plus/capture.py +51 -0
- devtorch_core/reasoning_plus/config.py +256 -0
- devtorch_core/reasoning_plus/context.py +262 -0
- devtorch_core/reasoning_plus/learning/__init__.py +72 -0
- devtorch_core/reasoning_plus/learning/analytics.py +141 -0
- devtorch_core/reasoning_plus/learning/api.py +313 -0
- devtorch_core/reasoning_plus/learning/chain.py +285 -0
- devtorch_core/reasoning_plus/learning/composer.py +74 -0
- devtorch_core/reasoning_plus/learning/cross_project.py +234 -0
- devtorch_core/reasoning_plus/learning/embeddings.py +209 -0
- devtorch_core/reasoning_plus/learning/extractor.py +207 -0
- devtorch_core/reasoning_plus/learning/models.py +116 -0
- devtorch_core/reasoning_plus/learning/provenance.py +126 -0
- devtorch_core/reasoning_plus/learning/recorder.py +81 -0
- devtorch_core/reasoning_plus/learning/relevance.py +122 -0
- devtorch_core/reasoning_plus/learning/state.py +86 -0
- devtorch_core/reasoning_plus/learning/store.py +160 -0
- devtorch_core/reasoning_plus/learning/theta_learning_bridge.py +94 -0
- devtorch_core/reasoning_plus/prompt.py +90 -0
- devtorch_core/rep.py +134 -0
- devtorch_core/rep_network/__init__.py +25 -0
- devtorch_core/rep_network/merge.py +70 -0
- devtorch_core/rep_network/node.py +137 -0
- devtorch_core/rep_network/server.py +140 -0
- devtorch_core/rep_network/sync.py +207 -0
- devtorch_core/sensitivity.py +182 -0
- devtorch_core/serve.py +258 -0
- devtorch_core/session/__init__.py +39 -0
- devtorch_core/session/disagreement.py +188 -0
- devtorch_core/session/models.py +114 -0
- devtorch_core/session/orchestrator.py +182 -0
- devtorch_core/session/planner.py +169 -0
- devtorch_core/session/simulator.py +132 -0
- devtorch_core/signing.py +290 -0
- devtorch_core/sis.py +197 -0
- devtorch_core/storage.py +308 -0
- devtorch_core/templates/__init__.py +6 -0
- devtorch_core/templates/engine.py +122 -0
- devtorch_core/templates/go.py +18 -0
- devtorch_core/templates/infra.py +19 -0
- devtorch_core/templates/library/__init__.py +18 -0
- devtorch_core/templates/library/api_design.md +27 -0
- devtorch_core/templates/library/bug_fix.md +27 -0
- devtorch_core/templates/library/decision_record.md +27 -0
- devtorch_core/templates/library/engine.py +228 -0
- devtorch_core/templates/library/security_review.md +30 -0
- devtorch_core/templates/python.py +19 -0
- devtorch_core/templates/react.py +18 -0
- devtorch_core/templates/typescript.py +18 -0
- devtorch_core/theta.py +221 -0
- devtorch_core/theta_synthesis.py +268 -0
- devtorch_core/topics.py +320 -0
- devtorch_core/variance.py +219 -0
- devtorch_core/wrapper/__init__.py +52 -0
- devtorch_core/wrapper/anthropic.py +487 -0
- devtorch_core/wrapper/base.py +562 -0
- devtorch_core/wrapper/bedrock.py +342 -0
- devtorch_core/wrapper/gemini.py +422 -0
- devtorch_core/wrapper/ollama.py +527 -0
- devtorch_core/wrapper/openai.py +461 -0
- devtorch_core-3.0.1.dist-info/METADATA +867 -0
- devtorch_core-3.0.1.dist-info/RECORD +193 -0
- devtorch_core-3.0.1.dist-info/WHEEL +5 -0
- devtorch_core-3.0.1.dist-info/entry_points.txt +2 -0
- 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
|
+
)
|