superqode 0.1.5__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 (288) hide show
  1. superqode/__init__.py +33 -0
  2. superqode/acp/__init__.py +23 -0
  3. superqode/acp/client.py +913 -0
  4. superqode/acp/permission_screen.py +457 -0
  5. superqode/acp/types.py +480 -0
  6. superqode/acp_discovery.py +856 -0
  7. superqode/agent/__init__.py +22 -0
  8. superqode/agent/edit_strategies.py +334 -0
  9. superqode/agent/loop.py +892 -0
  10. superqode/agent/qe_report_templates.py +39 -0
  11. superqode/agent/system_prompts.py +353 -0
  12. superqode/agent_output.py +721 -0
  13. superqode/agent_stream.py +953 -0
  14. superqode/agents/__init__.py +59 -0
  15. superqode/agents/acp_registry.py +305 -0
  16. superqode/agents/client.py +249 -0
  17. superqode/agents/data/augmentcode.com.toml +51 -0
  18. superqode/agents/data/cagent.dev.toml +51 -0
  19. superqode/agents/data/claude.com.toml +60 -0
  20. superqode/agents/data/codeassistant.dev.toml +51 -0
  21. superqode/agents/data/codex.openai.com.toml +57 -0
  22. superqode/agents/data/fastagent.ai.toml +66 -0
  23. superqode/agents/data/geminicli.com.toml +77 -0
  24. superqode/agents/data/goose.block.xyz.toml +54 -0
  25. superqode/agents/data/junie.jetbrains.com.toml +56 -0
  26. superqode/agents/data/kimi.moonshot.cn.toml +57 -0
  27. superqode/agents/data/llmlingagent.dev.toml +51 -0
  28. superqode/agents/data/molt.bot.toml +49 -0
  29. superqode/agents/data/opencode.ai.toml +60 -0
  30. superqode/agents/data/stakpak.dev.toml +51 -0
  31. superqode/agents/data/vtcode.dev.toml +51 -0
  32. superqode/agents/discovery.py +266 -0
  33. superqode/agents/messaging.py +160 -0
  34. superqode/agents/persona.py +166 -0
  35. superqode/agents/registry.py +421 -0
  36. superqode/agents/schema.py +72 -0
  37. superqode/agents/unified.py +367 -0
  38. superqode/app/__init__.py +111 -0
  39. superqode/app/constants.py +314 -0
  40. superqode/app/css.py +366 -0
  41. superqode/app/models.py +118 -0
  42. superqode/app/suggester.py +125 -0
  43. superqode/app/widgets.py +1591 -0
  44. superqode/app_enhanced.py +399 -0
  45. superqode/app_main.py +17187 -0
  46. superqode/approval.py +312 -0
  47. superqode/atomic.py +296 -0
  48. superqode/commands/__init__.py +1 -0
  49. superqode/commands/acp.py +965 -0
  50. superqode/commands/agents.py +180 -0
  51. superqode/commands/auth.py +278 -0
  52. superqode/commands/config.py +374 -0
  53. superqode/commands/init.py +826 -0
  54. superqode/commands/providers.py +819 -0
  55. superqode/commands/qe.py +1145 -0
  56. superqode/commands/roles.py +380 -0
  57. superqode/commands/serve.py +172 -0
  58. superqode/commands/suggestions.py +127 -0
  59. superqode/commands/superqe.py +460 -0
  60. superqode/config/__init__.py +51 -0
  61. superqode/config/loader.py +812 -0
  62. superqode/config/schema.py +498 -0
  63. superqode/core/__init__.py +111 -0
  64. superqode/core/roles.py +281 -0
  65. superqode/danger.py +386 -0
  66. superqode/data/superqode-template.yaml +1522 -0
  67. superqode/design_system.py +1080 -0
  68. superqode/dialogs/__init__.py +6 -0
  69. superqode/dialogs/base.py +39 -0
  70. superqode/dialogs/model.py +130 -0
  71. superqode/dialogs/provider.py +870 -0
  72. superqode/diff_view.py +919 -0
  73. superqode/enterprise.py +21 -0
  74. superqode/evaluation/__init__.py +25 -0
  75. superqode/evaluation/adapters.py +93 -0
  76. superqode/evaluation/behaviors.py +89 -0
  77. superqode/evaluation/engine.py +209 -0
  78. superqode/evaluation/scenarios.py +96 -0
  79. superqode/execution/__init__.py +36 -0
  80. superqode/execution/linter.py +538 -0
  81. superqode/execution/modes.py +347 -0
  82. superqode/execution/resolver.py +283 -0
  83. superqode/execution/runner.py +642 -0
  84. superqode/file_explorer.py +811 -0
  85. superqode/file_viewer.py +471 -0
  86. superqode/flash.py +183 -0
  87. superqode/guidance/__init__.py +58 -0
  88. superqode/guidance/config.py +203 -0
  89. superqode/guidance/prompts.py +71 -0
  90. superqode/harness/__init__.py +54 -0
  91. superqode/harness/accelerator.py +291 -0
  92. superqode/harness/config.py +319 -0
  93. superqode/harness/validator.py +147 -0
  94. superqode/history.py +279 -0
  95. superqode/integrations/superopt_runner.py +124 -0
  96. superqode/logging/__init__.py +49 -0
  97. superqode/logging/adapters.py +219 -0
  98. superqode/logging/formatter.py +923 -0
  99. superqode/logging/integration.py +341 -0
  100. superqode/logging/sinks.py +170 -0
  101. superqode/logging/unified_log.py +417 -0
  102. superqode/lsp/__init__.py +26 -0
  103. superqode/lsp/client.py +544 -0
  104. superqode/main.py +1069 -0
  105. superqode/mcp/__init__.py +89 -0
  106. superqode/mcp/auth_storage.py +380 -0
  107. superqode/mcp/client.py +1236 -0
  108. superqode/mcp/config.py +319 -0
  109. superqode/mcp/integration.py +337 -0
  110. superqode/mcp/oauth.py +436 -0
  111. superqode/mcp/oauth_callback.py +385 -0
  112. superqode/mcp/types.py +290 -0
  113. superqode/memory/__init__.py +31 -0
  114. superqode/memory/feedback.py +342 -0
  115. superqode/memory/store.py +522 -0
  116. superqode/notifications.py +369 -0
  117. superqode/optimization/__init__.py +5 -0
  118. superqode/optimization/config.py +33 -0
  119. superqode/permissions/__init__.py +25 -0
  120. superqode/permissions/rules.py +488 -0
  121. superqode/plan.py +323 -0
  122. superqode/providers/__init__.py +33 -0
  123. superqode/providers/gateway/__init__.py +165 -0
  124. superqode/providers/gateway/base.py +228 -0
  125. superqode/providers/gateway/litellm_gateway.py +1170 -0
  126. superqode/providers/gateway/openresponses_gateway.py +436 -0
  127. superqode/providers/health.py +297 -0
  128. superqode/providers/huggingface/__init__.py +74 -0
  129. superqode/providers/huggingface/downloader.py +472 -0
  130. superqode/providers/huggingface/endpoints.py +442 -0
  131. superqode/providers/huggingface/hub.py +531 -0
  132. superqode/providers/huggingface/inference.py +394 -0
  133. superqode/providers/huggingface/transformers_runner.py +516 -0
  134. superqode/providers/local/__init__.py +100 -0
  135. superqode/providers/local/base.py +438 -0
  136. superqode/providers/local/discovery.py +418 -0
  137. superqode/providers/local/lmstudio.py +256 -0
  138. superqode/providers/local/mlx.py +457 -0
  139. superqode/providers/local/ollama.py +486 -0
  140. superqode/providers/local/sglang.py +268 -0
  141. superqode/providers/local/tgi.py +260 -0
  142. superqode/providers/local/tool_support.py +477 -0
  143. superqode/providers/local/vllm.py +258 -0
  144. superqode/providers/manager.py +1338 -0
  145. superqode/providers/models.py +1016 -0
  146. superqode/providers/models_dev.py +578 -0
  147. superqode/providers/openresponses/__init__.py +87 -0
  148. superqode/providers/openresponses/converters/__init__.py +17 -0
  149. superqode/providers/openresponses/converters/messages.py +343 -0
  150. superqode/providers/openresponses/converters/tools.py +268 -0
  151. superqode/providers/openresponses/schema/__init__.py +56 -0
  152. superqode/providers/openresponses/schema/models.py +585 -0
  153. superqode/providers/openresponses/streaming/__init__.py +5 -0
  154. superqode/providers/openresponses/streaming/parser.py +338 -0
  155. superqode/providers/openresponses/tools/__init__.py +21 -0
  156. superqode/providers/openresponses/tools/apply_patch.py +352 -0
  157. superqode/providers/openresponses/tools/code_interpreter.py +290 -0
  158. superqode/providers/openresponses/tools/file_search.py +333 -0
  159. superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
  160. superqode/providers/registry.py +716 -0
  161. superqode/providers/usage.py +332 -0
  162. superqode/pure_mode.py +384 -0
  163. superqode/qr/__init__.py +23 -0
  164. superqode/qr/dashboard.py +781 -0
  165. superqode/qr/generator.py +1018 -0
  166. superqode/qr/templates.py +135 -0
  167. superqode/safety/__init__.py +41 -0
  168. superqode/safety/sandbox.py +413 -0
  169. superqode/safety/warnings.py +256 -0
  170. superqode/server/__init__.py +33 -0
  171. superqode/server/lsp_server.py +775 -0
  172. superqode/server/web.py +250 -0
  173. superqode/session/__init__.py +25 -0
  174. superqode/session/persistence.py +580 -0
  175. superqode/session/sharing.py +477 -0
  176. superqode/session.py +475 -0
  177. superqode/sidebar.py +2991 -0
  178. superqode/stream_view.py +648 -0
  179. superqode/styles/__init__.py +3 -0
  180. superqode/superqe/__init__.py +184 -0
  181. superqode/superqe/acp_runner.py +1064 -0
  182. superqode/superqe/constitution/__init__.py +62 -0
  183. superqode/superqe/constitution/evaluator.py +308 -0
  184. superqode/superqe/constitution/loader.py +432 -0
  185. superqode/superqe/constitution/schema.py +250 -0
  186. superqode/superqe/events.py +591 -0
  187. superqode/superqe/frameworks/__init__.py +65 -0
  188. superqode/superqe/frameworks/base.py +234 -0
  189. superqode/superqe/frameworks/e2e.py +263 -0
  190. superqode/superqe/frameworks/executor.py +237 -0
  191. superqode/superqe/frameworks/javascript.py +409 -0
  192. superqode/superqe/frameworks/python.py +373 -0
  193. superqode/superqe/frameworks/registry.py +92 -0
  194. superqode/superqe/mcp_tools/__init__.py +47 -0
  195. superqode/superqe/mcp_tools/core_tools.py +418 -0
  196. superqode/superqe/mcp_tools/registry.py +230 -0
  197. superqode/superqe/mcp_tools/testing_tools.py +167 -0
  198. superqode/superqe/noise.py +89 -0
  199. superqode/superqe/orchestrator.py +778 -0
  200. superqode/superqe/roles.py +609 -0
  201. superqode/superqe/session.py +713 -0
  202. superqode/superqe/skills/__init__.py +57 -0
  203. superqode/superqe/skills/base.py +106 -0
  204. superqode/superqe/skills/core_skills.py +899 -0
  205. superqode/superqe/skills/registry.py +90 -0
  206. superqode/superqe/verifier.py +101 -0
  207. superqode/superqe_cli.py +76 -0
  208. superqode/tool_call.py +358 -0
  209. superqode/tools/__init__.py +93 -0
  210. superqode/tools/agent_tools.py +496 -0
  211. superqode/tools/base.py +324 -0
  212. superqode/tools/batch_tool.py +133 -0
  213. superqode/tools/diagnostics.py +311 -0
  214. superqode/tools/edit_tools.py +653 -0
  215. superqode/tools/enhanced_base.py +515 -0
  216. superqode/tools/file_tools.py +269 -0
  217. superqode/tools/file_tracking.py +45 -0
  218. superqode/tools/lsp_tools.py +610 -0
  219. superqode/tools/network_tools.py +350 -0
  220. superqode/tools/permissions.py +400 -0
  221. superqode/tools/question_tool.py +324 -0
  222. superqode/tools/search_tools.py +598 -0
  223. superqode/tools/shell_tools.py +259 -0
  224. superqode/tools/todo_tools.py +121 -0
  225. superqode/tools/validation.py +80 -0
  226. superqode/tools/web_tools.py +639 -0
  227. superqode/tui.py +1152 -0
  228. superqode/tui_integration.py +875 -0
  229. superqode/tui_widgets/__init__.py +27 -0
  230. superqode/tui_widgets/widgets/__init__.py +18 -0
  231. superqode/tui_widgets/widgets/progress.py +185 -0
  232. superqode/tui_widgets/widgets/tool_display.py +188 -0
  233. superqode/undo_manager.py +574 -0
  234. superqode/utils/__init__.py +5 -0
  235. superqode/utils/error_handling.py +323 -0
  236. superqode/utils/fuzzy.py +257 -0
  237. superqode/widgets/__init__.py +477 -0
  238. superqode/widgets/agent_collab.py +390 -0
  239. superqode/widgets/agent_store.py +936 -0
  240. superqode/widgets/agent_switcher.py +395 -0
  241. superqode/widgets/animation_manager.py +284 -0
  242. superqode/widgets/code_context.py +356 -0
  243. superqode/widgets/command_palette.py +412 -0
  244. superqode/widgets/connection_status.py +537 -0
  245. superqode/widgets/conversation_history.py +470 -0
  246. superqode/widgets/diff_indicator.py +155 -0
  247. superqode/widgets/enhanced_status_bar.py +385 -0
  248. superqode/widgets/enhanced_toast.py +476 -0
  249. superqode/widgets/file_browser.py +809 -0
  250. superqode/widgets/file_reference.py +585 -0
  251. superqode/widgets/issue_timeline.py +340 -0
  252. superqode/widgets/leader_key.py +264 -0
  253. superqode/widgets/mode_switcher.py +445 -0
  254. superqode/widgets/model_picker.py +234 -0
  255. superqode/widgets/permission_preview.py +1205 -0
  256. superqode/widgets/prompt.py +358 -0
  257. superqode/widgets/provider_connect.py +725 -0
  258. superqode/widgets/pty_shell.py +587 -0
  259. superqode/widgets/qe_dashboard.py +321 -0
  260. superqode/widgets/resizable_sidebar.py +377 -0
  261. superqode/widgets/response_changes.py +218 -0
  262. superqode/widgets/response_display.py +528 -0
  263. superqode/widgets/rich_tool_display.py +613 -0
  264. superqode/widgets/sidebar_panels.py +1180 -0
  265. superqode/widgets/slash_complete.py +356 -0
  266. superqode/widgets/split_view.py +612 -0
  267. superqode/widgets/status_bar.py +273 -0
  268. superqode/widgets/superqode_display.py +786 -0
  269. superqode/widgets/thinking_display.py +815 -0
  270. superqode/widgets/throbber.py +87 -0
  271. superqode/widgets/toast.py +206 -0
  272. superqode/widgets/unified_output.py +1073 -0
  273. superqode/workspace/__init__.py +75 -0
  274. superqode/workspace/artifacts.py +472 -0
  275. superqode/workspace/coordinator.py +353 -0
  276. superqode/workspace/diff_tracker.py +429 -0
  277. superqode/workspace/git_guard.py +373 -0
  278. superqode/workspace/git_snapshot.py +526 -0
  279. superqode/workspace/manager.py +750 -0
  280. superqode/workspace/snapshot.py +357 -0
  281. superqode/workspace/watcher.py +535 -0
  282. superqode/workspace/worktree.py +440 -0
  283. superqode-0.1.5.dist-info/METADATA +204 -0
  284. superqode-0.1.5.dist-info/RECORD +288 -0
  285. superqode-0.1.5.dist-info/WHEEL +5 -0
  286. superqode-0.1.5.dist-info/entry_points.txt +3 -0
  287. superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
  288. superqode-0.1.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,62 @@
1
+ """
2
+ Constitution System - Quality rules and guardrails.
3
+
4
+ Provides a declarative system for defining:
5
+ - Quality principles and policies
6
+ - Enforcement rules with actions
7
+ - Metrics and thresholds
8
+ - Quality gates
9
+
10
+ Based on customizable YAML/JSON configuration files.
11
+ """
12
+
13
+ from .schema import (
14
+ Constitution,
15
+ Principle,
16
+ Rule,
17
+ Condition,
18
+ Action,
19
+ Metric,
20
+ Threshold,
21
+ PriorityLevel,
22
+ ActionType,
23
+ SeverityLevel,
24
+ ConditionOperator,
25
+ ThresholdMode,
26
+ )
27
+ from .loader import (
28
+ ConstitutionLoader,
29
+ load_constitution,
30
+ get_default_constitution,
31
+ )
32
+ from .evaluator import (
33
+ ConstitutionEvaluator,
34
+ EvaluationResult,
35
+ RuleViolation,
36
+ evaluate_against_constitution,
37
+ )
38
+
39
+ __all__ = [
40
+ # Schema
41
+ "Constitution",
42
+ "Principle",
43
+ "Rule",
44
+ "Condition",
45
+ "Action",
46
+ "Metric",
47
+ "Threshold",
48
+ "PriorityLevel",
49
+ "ActionType",
50
+ "SeverityLevel",
51
+ "ConditionOperator",
52
+ "ThresholdMode",
53
+ # Loader
54
+ "ConstitutionLoader",
55
+ "load_constitution",
56
+ "get_default_constitution",
57
+ # Evaluator
58
+ "ConstitutionEvaluator",
59
+ "EvaluationResult",
60
+ "RuleViolation",
61
+ "evaluate_against_constitution",
62
+ ]
@@ -0,0 +1,308 @@
1
+ """
2
+ Constitution Evaluator - Evaluate code/tests against constitution.
3
+
4
+ Provides:
5
+ - Rule evaluation
6
+ - Threshold checking
7
+ - Violation reporting
8
+ - Quality gate assessment
9
+ """
10
+
11
+ from dataclasses import dataclass, field
12
+ from typing import Any, Dict, List, Optional
13
+ import logging
14
+
15
+ from .schema import (
16
+ Constitution,
17
+ Rule,
18
+ Threshold,
19
+ Action,
20
+ ActionType,
21
+ SeverityLevel,
22
+ )
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ @dataclass
28
+ class RuleViolation:
29
+ """A rule violation."""
30
+
31
+ rule_id: str
32
+ rule_name: str
33
+ principle_id: str
34
+ message: str
35
+ severity: SeverityLevel
36
+ action: Action
37
+ context: Dict[str, Any] = field(default_factory=dict)
38
+ remediation: Optional[str] = None
39
+
40
+
41
+ @dataclass
42
+ class ThresholdViolation:
43
+ """A threshold violation."""
44
+
45
+ threshold_id: str
46
+ threshold_name: str
47
+ metric_id: str
48
+ expected_value: float
49
+ actual_value: float
50
+ blocking: bool
51
+ message: str
52
+
53
+
54
+ @dataclass
55
+ class EvaluationResult:
56
+ """Result of evaluating against a constitution."""
57
+
58
+ constitution_name: str
59
+ constitution_version: str
60
+ passed: bool
61
+ rule_violations: List[RuleViolation] = field(default_factory=list)
62
+ threshold_violations: List[ThresholdViolation] = field(default_factory=list)
63
+ metrics_evaluated: Dict[str, Any] = field(default_factory=dict)
64
+ warnings: List[str] = field(default_factory=list)
65
+
66
+ @property
67
+ def blocking_violations(self) -> int:
68
+ """Count of blocking violations."""
69
+ blocking_rules = sum(1 for v in self.rule_violations if v.action.type == ActionType.BLOCK)
70
+ blocking_thresholds = sum(1 for v in self.threshold_violations if v.blocking)
71
+ return blocking_rules + blocking_thresholds
72
+
73
+ @property
74
+ def can_deploy(self) -> bool:
75
+ """Check if deployment is allowed."""
76
+ return self.blocking_violations == 0
77
+
78
+ def to_dict(self) -> Dict[str, Any]:
79
+ """Convert to dictionary."""
80
+ return {
81
+ "constitution": self.constitution_name,
82
+ "version": self.constitution_version,
83
+ "passed": self.passed,
84
+ "can_deploy": self.can_deploy,
85
+ "blocking_violations": self.blocking_violations,
86
+ "rule_violations": [
87
+ {
88
+ "rule_id": v.rule_id,
89
+ "rule_name": v.rule_name,
90
+ "message": v.message,
91
+ "severity": v.severity.value,
92
+ "action": v.action.type.value,
93
+ "remediation": v.remediation,
94
+ }
95
+ for v in self.rule_violations
96
+ ],
97
+ "threshold_violations": [
98
+ {
99
+ "threshold_id": v.threshold_id,
100
+ "threshold_name": v.threshold_name,
101
+ "expected": v.expected_value,
102
+ "actual": v.actual_value,
103
+ "blocking": v.blocking,
104
+ }
105
+ for v in self.threshold_violations
106
+ ],
107
+ "metrics": self.metrics_evaluated,
108
+ "warnings": self.warnings,
109
+ }
110
+
111
+
112
+ class ConstitutionEvaluator:
113
+ """
114
+ Evaluator for constitutions.
115
+
116
+ Evaluates code and test results against constitution
117
+ rules and thresholds.
118
+ """
119
+
120
+ def __init__(self, constitution: Constitution):
121
+ """Initialize with a constitution."""
122
+ self.constitution = constitution
123
+
124
+ def evaluate(
125
+ self, context: Dict[str, Any], environment: Optional[str] = None
126
+ ) -> EvaluationResult:
127
+ """
128
+ Evaluate context against the constitution.
129
+
130
+ Args:
131
+ context: Dictionary with metrics and state to evaluate
132
+ environment: Optional environment name for filtering
133
+
134
+ Returns:
135
+ EvaluationResult with violations and assessment
136
+ """
137
+ result = EvaluationResult(
138
+ constitution_name=self.constitution.name,
139
+ constitution_version=self.constitution.version,
140
+ passed=True,
141
+ )
142
+
143
+ # Evaluate rules
144
+ for rule in self.constitution.get_enabled_rules():
145
+ # Skip if not applicable to environment
146
+ if environment and rule.environments and environment not in rule.environments:
147
+ continue
148
+
149
+ if not rule.evaluate(context):
150
+ violation = RuleViolation(
151
+ rule_id=rule.id,
152
+ rule_name=rule.name,
153
+ principle_id=rule.principle_id,
154
+ message=rule.action.message or f"Rule {rule.id} violated",
155
+ severity=rule.severity,
156
+ action=rule.action,
157
+ context=context,
158
+ remediation=rule.action.remediation,
159
+ )
160
+ result.rule_violations.append(violation)
161
+
162
+ if rule.action.type == ActionType.BLOCK:
163
+ result.passed = False
164
+ elif rule.action.type == ActionType.WARN:
165
+ result.warnings.append(violation.message)
166
+
167
+ # Evaluate thresholds
168
+ for threshold in self.constitution.thresholds:
169
+ # Skip if not applicable to environment
170
+ if environment and threshold.environments and environment not in threshold.environments:
171
+ continue
172
+
173
+ # Get metric value from context
174
+ metric = self.constitution.get_metric(threshold.metric_id)
175
+ if not metric:
176
+ continue
177
+
178
+ metric_value = self._get_metric_value(context, metric.id)
179
+ if metric_value is None:
180
+ result.warnings.append(f"Metric {metric.id} not found in context")
181
+ continue
182
+
183
+ result.metrics_evaluated[metric.id] = metric_value
184
+
185
+ # Evaluate threshold
186
+ passes = self._evaluate_threshold(threshold, metric_value)
187
+
188
+ if not passes:
189
+ violation = ThresholdViolation(
190
+ threshold_id=threshold.id,
191
+ threshold_name=threshold.name,
192
+ metric_id=threshold.metric_id,
193
+ expected_value=threshold.value,
194
+ actual_value=metric_value,
195
+ blocking=threshold.blocking,
196
+ message=f"{threshold.name}: expected {threshold.value}, got {metric_value}",
197
+ )
198
+ result.threshold_violations.append(violation)
199
+
200
+ if threshold.blocking:
201
+ result.passed = False
202
+
203
+ return result
204
+
205
+ def _get_metric_value(self, context: Dict[str, Any], metric_id: str) -> Optional[float]:
206
+ """Get metric value from context."""
207
+ # Try direct access
208
+ if metric_id in context:
209
+ return context[metric_id]
210
+
211
+ # Try nested access (metrics.coverage, etc.)
212
+ metrics = context.get("metrics", {})
213
+ if metric_id in metrics:
214
+ return metrics[metric_id]
215
+
216
+ # Try by metric name patterns
217
+ patterns = {
218
+ "M001": ["coverage", "coverage.percentage", "test_coverage"],
219
+ "M002": ["complexity", "complexity.avg", "cyclomatic_complexity"],
220
+ "M003": ["security", "security.vulnerabilities", "vulnerability_count"],
221
+ }
222
+
223
+ for pattern in patterns.get(metric_id, []):
224
+ parts = pattern.split(".")
225
+ value = context
226
+ for part in parts:
227
+ if isinstance(value, dict):
228
+ value = value.get(part)
229
+ else:
230
+ value = None
231
+ break
232
+ if value is not None:
233
+ return float(value)
234
+
235
+ return None
236
+
237
+ def _evaluate_threshold(self, threshold: Threshold, value: float) -> bool:
238
+ """Evaluate a threshold condition."""
239
+ from .schema import ConditionOperator
240
+
241
+ target = threshold.value
242
+
243
+ if threshold.operator == ConditionOperator.EQUALS:
244
+ return value == target
245
+ elif threshold.operator == ConditionOperator.NOT_EQUALS:
246
+ return value != target
247
+ elif threshold.operator == ConditionOperator.GREATER_THAN:
248
+ return value > target
249
+ elif threshold.operator == ConditionOperator.GREATER_THAN_OR_EQUAL:
250
+ return value >= target
251
+ elif threshold.operator == ConditionOperator.LESS_THAN:
252
+ return value < target
253
+ elif threshold.operator == ConditionOperator.LESS_THAN_OR_EQUAL:
254
+ return value <= target
255
+
256
+ return True
257
+
258
+ def get_applicable_rules(self, environment: Optional[str] = None) -> List[Rule]:
259
+ """Get rules applicable to an environment."""
260
+ rules = []
261
+ for rule in self.constitution.get_enabled_rules():
262
+ if not environment or not rule.environments or environment in rule.environments:
263
+ rules.append(rule)
264
+ return rules
265
+
266
+ def get_blocking_thresholds(self, environment: Optional[str] = None) -> List[Threshold]:
267
+ """Get blocking thresholds for an environment."""
268
+ thresholds = []
269
+ for threshold in self.constitution.get_blocking_thresholds():
270
+ if (
271
+ not environment
272
+ or not threshold.environments
273
+ or environment in threshold.environments
274
+ ):
275
+ thresholds.append(threshold)
276
+ return thresholds
277
+
278
+
279
+ def evaluate_against_constitution(
280
+ context: Dict[str, Any],
281
+ constitution: Optional[Constitution] = None,
282
+ constitution_path: Optional[str] = None,
283
+ environment: Optional[str] = None,
284
+ ) -> EvaluationResult:
285
+ """
286
+ Evaluate context against a constitution.
287
+
288
+ Args:
289
+ context: Metrics and state to evaluate
290
+ constitution: Constitution to use (or load from path)
291
+ constitution_path: Path to constitution file
292
+ environment: Environment for filtering rules
293
+
294
+ Returns:
295
+ EvaluationResult
296
+ """
297
+ if constitution is None:
298
+ if constitution_path:
299
+ from .loader import load_constitution
300
+
301
+ constitution = load_constitution(constitution_path)
302
+ else:
303
+ from .loader import get_default_constitution
304
+
305
+ constitution = get_default_constitution()
306
+
307
+ evaluator = ConstitutionEvaluator(constitution)
308
+ return evaluator.evaluate(context, environment)