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.
- superqode/__init__.py +33 -0
- superqode/acp/__init__.py +23 -0
- superqode/acp/client.py +913 -0
- superqode/acp/permission_screen.py +457 -0
- superqode/acp/types.py +480 -0
- superqode/acp_discovery.py +856 -0
- superqode/agent/__init__.py +22 -0
- superqode/agent/edit_strategies.py +334 -0
- superqode/agent/loop.py +892 -0
- superqode/agent/qe_report_templates.py +39 -0
- superqode/agent/system_prompts.py +353 -0
- superqode/agent_output.py +721 -0
- superqode/agent_stream.py +953 -0
- superqode/agents/__init__.py +59 -0
- superqode/agents/acp_registry.py +305 -0
- superqode/agents/client.py +249 -0
- superqode/agents/data/augmentcode.com.toml +51 -0
- superqode/agents/data/cagent.dev.toml +51 -0
- superqode/agents/data/claude.com.toml +60 -0
- superqode/agents/data/codeassistant.dev.toml +51 -0
- superqode/agents/data/codex.openai.com.toml +57 -0
- superqode/agents/data/fastagent.ai.toml +66 -0
- superqode/agents/data/geminicli.com.toml +77 -0
- superqode/agents/data/goose.block.xyz.toml +54 -0
- superqode/agents/data/junie.jetbrains.com.toml +56 -0
- superqode/agents/data/kimi.moonshot.cn.toml +57 -0
- superqode/agents/data/llmlingagent.dev.toml +51 -0
- superqode/agents/data/molt.bot.toml +49 -0
- superqode/agents/data/opencode.ai.toml +60 -0
- superqode/agents/data/stakpak.dev.toml +51 -0
- superqode/agents/data/vtcode.dev.toml +51 -0
- superqode/agents/discovery.py +266 -0
- superqode/agents/messaging.py +160 -0
- superqode/agents/persona.py +166 -0
- superqode/agents/registry.py +421 -0
- superqode/agents/schema.py +72 -0
- superqode/agents/unified.py +367 -0
- superqode/app/__init__.py +111 -0
- superqode/app/constants.py +314 -0
- superqode/app/css.py +366 -0
- superqode/app/models.py +118 -0
- superqode/app/suggester.py +125 -0
- superqode/app/widgets.py +1591 -0
- superqode/app_enhanced.py +399 -0
- superqode/app_main.py +17187 -0
- superqode/approval.py +312 -0
- superqode/atomic.py +296 -0
- superqode/commands/__init__.py +1 -0
- superqode/commands/acp.py +965 -0
- superqode/commands/agents.py +180 -0
- superqode/commands/auth.py +278 -0
- superqode/commands/config.py +374 -0
- superqode/commands/init.py +826 -0
- superqode/commands/providers.py +819 -0
- superqode/commands/qe.py +1145 -0
- superqode/commands/roles.py +380 -0
- superqode/commands/serve.py +172 -0
- superqode/commands/suggestions.py +127 -0
- superqode/commands/superqe.py +460 -0
- superqode/config/__init__.py +51 -0
- superqode/config/loader.py +812 -0
- superqode/config/schema.py +498 -0
- superqode/core/__init__.py +111 -0
- superqode/core/roles.py +281 -0
- superqode/danger.py +386 -0
- superqode/data/superqode-template.yaml +1522 -0
- superqode/design_system.py +1080 -0
- superqode/dialogs/__init__.py +6 -0
- superqode/dialogs/base.py +39 -0
- superqode/dialogs/model.py +130 -0
- superqode/dialogs/provider.py +870 -0
- superqode/diff_view.py +919 -0
- superqode/enterprise.py +21 -0
- superqode/evaluation/__init__.py +25 -0
- superqode/evaluation/adapters.py +93 -0
- superqode/evaluation/behaviors.py +89 -0
- superqode/evaluation/engine.py +209 -0
- superqode/evaluation/scenarios.py +96 -0
- superqode/execution/__init__.py +36 -0
- superqode/execution/linter.py +538 -0
- superqode/execution/modes.py +347 -0
- superqode/execution/resolver.py +283 -0
- superqode/execution/runner.py +642 -0
- superqode/file_explorer.py +811 -0
- superqode/file_viewer.py +471 -0
- superqode/flash.py +183 -0
- superqode/guidance/__init__.py +58 -0
- superqode/guidance/config.py +203 -0
- superqode/guidance/prompts.py +71 -0
- superqode/harness/__init__.py +54 -0
- superqode/harness/accelerator.py +291 -0
- superqode/harness/config.py +319 -0
- superqode/harness/validator.py +147 -0
- superqode/history.py +279 -0
- superqode/integrations/superopt_runner.py +124 -0
- superqode/logging/__init__.py +49 -0
- superqode/logging/adapters.py +219 -0
- superqode/logging/formatter.py +923 -0
- superqode/logging/integration.py +341 -0
- superqode/logging/sinks.py +170 -0
- superqode/logging/unified_log.py +417 -0
- superqode/lsp/__init__.py +26 -0
- superqode/lsp/client.py +544 -0
- superqode/main.py +1069 -0
- superqode/mcp/__init__.py +89 -0
- superqode/mcp/auth_storage.py +380 -0
- superqode/mcp/client.py +1236 -0
- superqode/mcp/config.py +319 -0
- superqode/mcp/integration.py +337 -0
- superqode/mcp/oauth.py +436 -0
- superqode/mcp/oauth_callback.py +385 -0
- superqode/mcp/types.py +290 -0
- superqode/memory/__init__.py +31 -0
- superqode/memory/feedback.py +342 -0
- superqode/memory/store.py +522 -0
- superqode/notifications.py +369 -0
- superqode/optimization/__init__.py +5 -0
- superqode/optimization/config.py +33 -0
- superqode/permissions/__init__.py +25 -0
- superqode/permissions/rules.py +488 -0
- superqode/plan.py +323 -0
- superqode/providers/__init__.py +33 -0
- superqode/providers/gateway/__init__.py +165 -0
- superqode/providers/gateway/base.py +228 -0
- superqode/providers/gateway/litellm_gateway.py +1170 -0
- superqode/providers/gateway/openresponses_gateway.py +436 -0
- superqode/providers/health.py +297 -0
- superqode/providers/huggingface/__init__.py +74 -0
- superqode/providers/huggingface/downloader.py +472 -0
- superqode/providers/huggingface/endpoints.py +442 -0
- superqode/providers/huggingface/hub.py +531 -0
- superqode/providers/huggingface/inference.py +394 -0
- superqode/providers/huggingface/transformers_runner.py +516 -0
- superqode/providers/local/__init__.py +100 -0
- superqode/providers/local/base.py +438 -0
- superqode/providers/local/discovery.py +418 -0
- superqode/providers/local/lmstudio.py +256 -0
- superqode/providers/local/mlx.py +457 -0
- superqode/providers/local/ollama.py +486 -0
- superqode/providers/local/sglang.py +268 -0
- superqode/providers/local/tgi.py +260 -0
- superqode/providers/local/tool_support.py +477 -0
- superqode/providers/local/vllm.py +258 -0
- superqode/providers/manager.py +1338 -0
- superqode/providers/models.py +1016 -0
- superqode/providers/models_dev.py +578 -0
- superqode/providers/openresponses/__init__.py +87 -0
- superqode/providers/openresponses/converters/__init__.py +17 -0
- superqode/providers/openresponses/converters/messages.py +343 -0
- superqode/providers/openresponses/converters/tools.py +268 -0
- superqode/providers/openresponses/schema/__init__.py +56 -0
- superqode/providers/openresponses/schema/models.py +585 -0
- superqode/providers/openresponses/streaming/__init__.py +5 -0
- superqode/providers/openresponses/streaming/parser.py +338 -0
- superqode/providers/openresponses/tools/__init__.py +21 -0
- superqode/providers/openresponses/tools/apply_patch.py +352 -0
- superqode/providers/openresponses/tools/code_interpreter.py +290 -0
- superqode/providers/openresponses/tools/file_search.py +333 -0
- superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
- superqode/providers/registry.py +716 -0
- superqode/providers/usage.py +332 -0
- superqode/pure_mode.py +384 -0
- superqode/qr/__init__.py +23 -0
- superqode/qr/dashboard.py +781 -0
- superqode/qr/generator.py +1018 -0
- superqode/qr/templates.py +135 -0
- superqode/safety/__init__.py +41 -0
- superqode/safety/sandbox.py +413 -0
- superqode/safety/warnings.py +256 -0
- superqode/server/__init__.py +33 -0
- superqode/server/lsp_server.py +775 -0
- superqode/server/web.py +250 -0
- superqode/session/__init__.py +25 -0
- superqode/session/persistence.py +580 -0
- superqode/session/sharing.py +477 -0
- superqode/session.py +475 -0
- superqode/sidebar.py +2991 -0
- superqode/stream_view.py +648 -0
- superqode/styles/__init__.py +3 -0
- superqode/superqe/__init__.py +184 -0
- superqode/superqe/acp_runner.py +1064 -0
- superqode/superqe/constitution/__init__.py +62 -0
- superqode/superqe/constitution/evaluator.py +308 -0
- superqode/superqe/constitution/loader.py +432 -0
- superqode/superqe/constitution/schema.py +250 -0
- superqode/superqe/events.py +591 -0
- superqode/superqe/frameworks/__init__.py +65 -0
- superqode/superqe/frameworks/base.py +234 -0
- superqode/superqe/frameworks/e2e.py +263 -0
- superqode/superqe/frameworks/executor.py +237 -0
- superqode/superqe/frameworks/javascript.py +409 -0
- superqode/superqe/frameworks/python.py +373 -0
- superqode/superqe/frameworks/registry.py +92 -0
- superqode/superqe/mcp_tools/__init__.py +47 -0
- superqode/superqe/mcp_tools/core_tools.py +418 -0
- superqode/superqe/mcp_tools/registry.py +230 -0
- superqode/superqe/mcp_tools/testing_tools.py +167 -0
- superqode/superqe/noise.py +89 -0
- superqode/superqe/orchestrator.py +778 -0
- superqode/superqe/roles.py +609 -0
- superqode/superqe/session.py +713 -0
- superqode/superqe/skills/__init__.py +57 -0
- superqode/superqe/skills/base.py +106 -0
- superqode/superqe/skills/core_skills.py +899 -0
- superqode/superqe/skills/registry.py +90 -0
- superqode/superqe/verifier.py +101 -0
- superqode/superqe_cli.py +76 -0
- superqode/tool_call.py +358 -0
- superqode/tools/__init__.py +93 -0
- superqode/tools/agent_tools.py +496 -0
- superqode/tools/base.py +324 -0
- superqode/tools/batch_tool.py +133 -0
- superqode/tools/diagnostics.py +311 -0
- superqode/tools/edit_tools.py +653 -0
- superqode/tools/enhanced_base.py +515 -0
- superqode/tools/file_tools.py +269 -0
- superqode/tools/file_tracking.py +45 -0
- superqode/tools/lsp_tools.py +610 -0
- superqode/tools/network_tools.py +350 -0
- superqode/tools/permissions.py +400 -0
- superqode/tools/question_tool.py +324 -0
- superqode/tools/search_tools.py +598 -0
- superqode/tools/shell_tools.py +259 -0
- superqode/tools/todo_tools.py +121 -0
- superqode/tools/validation.py +80 -0
- superqode/tools/web_tools.py +639 -0
- superqode/tui.py +1152 -0
- superqode/tui_integration.py +875 -0
- superqode/tui_widgets/__init__.py +27 -0
- superqode/tui_widgets/widgets/__init__.py +18 -0
- superqode/tui_widgets/widgets/progress.py +185 -0
- superqode/tui_widgets/widgets/tool_display.py +188 -0
- superqode/undo_manager.py +574 -0
- superqode/utils/__init__.py +5 -0
- superqode/utils/error_handling.py +323 -0
- superqode/utils/fuzzy.py +257 -0
- superqode/widgets/__init__.py +477 -0
- superqode/widgets/agent_collab.py +390 -0
- superqode/widgets/agent_store.py +936 -0
- superqode/widgets/agent_switcher.py +395 -0
- superqode/widgets/animation_manager.py +284 -0
- superqode/widgets/code_context.py +356 -0
- superqode/widgets/command_palette.py +412 -0
- superqode/widgets/connection_status.py +537 -0
- superqode/widgets/conversation_history.py +470 -0
- superqode/widgets/diff_indicator.py +155 -0
- superqode/widgets/enhanced_status_bar.py +385 -0
- superqode/widgets/enhanced_toast.py +476 -0
- superqode/widgets/file_browser.py +809 -0
- superqode/widgets/file_reference.py +585 -0
- superqode/widgets/issue_timeline.py +340 -0
- superqode/widgets/leader_key.py +264 -0
- superqode/widgets/mode_switcher.py +445 -0
- superqode/widgets/model_picker.py +234 -0
- superqode/widgets/permission_preview.py +1205 -0
- superqode/widgets/prompt.py +358 -0
- superqode/widgets/provider_connect.py +725 -0
- superqode/widgets/pty_shell.py +587 -0
- superqode/widgets/qe_dashboard.py +321 -0
- superqode/widgets/resizable_sidebar.py +377 -0
- superqode/widgets/response_changes.py +218 -0
- superqode/widgets/response_display.py +528 -0
- superqode/widgets/rich_tool_display.py +613 -0
- superqode/widgets/sidebar_panels.py +1180 -0
- superqode/widgets/slash_complete.py +356 -0
- superqode/widgets/split_view.py +612 -0
- superqode/widgets/status_bar.py +273 -0
- superqode/widgets/superqode_display.py +786 -0
- superqode/widgets/thinking_display.py +815 -0
- superqode/widgets/throbber.py +87 -0
- superqode/widgets/toast.py +206 -0
- superqode/widgets/unified_output.py +1073 -0
- superqode/workspace/__init__.py +75 -0
- superqode/workspace/artifacts.py +472 -0
- superqode/workspace/coordinator.py +353 -0
- superqode/workspace/diff_tracker.py +429 -0
- superqode/workspace/git_guard.py +373 -0
- superqode/workspace/git_snapshot.py +526 -0
- superqode/workspace/manager.py +750 -0
- superqode/workspace/snapshot.py +357 -0
- superqode/workspace/watcher.py +535 -0
- superqode/workspace/worktree.py +440 -0
- superqode-0.1.5.dist-info/METADATA +204 -0
- superqode-0.1.5.dist-info/RECORD +288 -0
- superqode-0.1.5.dist-info/WHEEL +5 -0
- superqode-0.1.5.dist-info/entry_points.txt +3 -0
- superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
- 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)
|