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,432 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Constitution Loader - Load and parse constitution files.
|
|
3
|
+
|
|
4
|
+
Supports:
|
|
5
|
+
- YAML and JSON formats
|
|
6
|
+
- Inheritance (extends)
|
|
7
|
+
- Merging strategies
|
|
8
|
+
- Validation
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
13
|
+
import json
|
|
14
|
+
import logging
|
|
15
|
+
|
|
16
|
+
from .schema import (
|
|
17
|
+
Constitution,
|
|
18
|
+
Principle,
|
|
19
|
+
Rule,
|
|
20
|
+
Condition,
|
|
21
|
+
Action,
|
|
22
|
+
Metric,
|
|
23
|
+
Threshold,
|
|
24
|
+
PriorityLevel,
|
|
25
|
+
ActionType,
|
|
26
|
+
SeverityLevel,
|
|
27
|
+
ConditionOperator,
|
|
28
|
+
ThresholdMode,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ConstitutionLoader:
|
|
35
|
+
"""
|
|
36
|
+
Loader for constitution files.
|
|
37
|
+
|
|
38
|
+
Supports YAML and JSON formats with inheritance.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
_cache: Dict[str, Constitution] = {}
|
|
42
|
+
|
|
43
|
+
def __init__(self, search_paths: Optional[List[Path]] = None):
|
|
44
|
+
"""Initialize with optional search paths."""
|
|
45
|
+
self.search_paths = search_paths or [
|
|
46
|
+
Path.cwd() / ".superqode",
|
|
47
|
+
Path.cwd(),
|
|
48
|
+
Path.home() / ".superqode",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
def load(self, path: str) -> Constitution:
|
|
52
|
+
"""
|
|
53
|
+
Load a constitution from a file.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
path: Path to constitution file or name to search
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Loaded Constitution
|
|
60
|
+
"""
|
|
61
|
+
# Check cache
|
|
62
|
+
if path in self._cache:
|
|
63
|
+
return self._cache[path]
|
|
64
|
+
|
|
65
|
+
# Find the file
|
|
66
|
+
file_path = self._find_file(path)
|
|
67
|
+
if not file_path:
|
|
68
|
+
raise FileNotFoundError(f"Constitution not found: {path}")
|
|
69
|
+
|
|
70
|
+
# Load and parse
|
|
71
|
+
constitution = self._load_file(file_path)
|
|
72
|
+
|
|
73
|
+
# Handle inheritance
|
|
74
|
+
if constitution.extends:
|
|
75
|
+
parent = self.load(constitution.extends)
|
|
76
|
+
constitution = self._merge(parent, constitution)
|
|
77
|
+
|
|
78
|
+
# Cache and return
|
|
79
|
+
self._cache[path] = constitution
|
|
80
|
+
return constitution
|
|
81
|
+
|
|
82
|
+
def _find_file(self, path: str) -> Optional[Path]:
|
|
83
|
+
"""Find a constitution file."""
|
|
84
|
+
# Direct path
|
|
85
|
+
direct = Path(path)
|
|
86
|
+
if direct.exists():
|
|
87
|
+
return direct
|
|
88
|
+
|
|
89
|
+
# Search in search paths
|
|
90
|
+
for search_path in self.search_paths:
|
|
91
|
+
for ext in ["", ".yaml", ".yml", ".json"]:
|
|
92
|
+
candidate = search_path / f"{path}{ext}"
|
|
93
|
+
if candidate.exists():
|
|
94
|
+
return candidate
|
|
95
|
+
|
|
96
|
+
# Also try constitution subdirectory
|
|
97
|
+
for ext in ["", ".yaml", ".yml", ".json"]:
|
|
98
|
+
candidate = search_path / "constitution" / f"{path}{ext}"
|
|
99
|
+
if candidate.exists():
|
|
100
|
+
return candidate
|
|
101
|
+
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
def _load_file(self, path: Path) -> Constitution:
|
|
105
|
+
"""Load a constitution from a file."""
|
|
106
|
+
content = path.read_text()
|
|
107
|
+
|
|
108
|
+
if path.suffix == ".json":
|
|
109
|
+
data = json.loads(content)
|
|
110
|
+
else:
|
|
111
|
+
import yaml
|
|
112
|
+
|
|
113
|
+
data = yaml.safe_load(content)
|
|
114
|
+
|
|
115
|
+
return self._parse_constitution(data)
|
|
116
|
+
|
|
117
|
+
def _parse_constitution(self, data: Dict[str, Any]) -> Constitution:
|
|
118
|
+
"""Parse constitution from dictionary."""
|
|
119
|
+
# Parse principles
|
|
120
|
+
principles = []
|
|
121
|
+
for p_data in data.get("principles", []):
|
|
122
|
+
principles.append(
|
|
123
|
+
Principle(
|
|
124
|
+
id=p_data["id"],
|
|
125
|
+
name=p_data["name"],
|
|
126
|
+
description=p_data.get("description", ""),
|
|
127
|
+
priority=PriorityLevel(p_data.get("priority", "medium")),
|
|
128
|
+
category=p_data.get("category", "general"),
|
|
129
|
+
mandatory=p_data.get("mandatory", False),
|
|
130
|
+
related_principles=p_data.get("related_principles", []),
|
|
131
|
+
tags=p_data.get("tags", []),
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Parse rules
|
|
136
|
+
rules = []
|
|
137
|
+
for r_data in data.get("rules", []):
|
|
138
|
+
conditions = []
|
|
139
|
+
for c_data in r_data.get("conditions", []):
|
|
140
|
+
conditions.append(
|
|
141
|
+
Condition(
|
|
142
|
+
field=c_data["field"],
|
|
143
|
+
operator=ConditionOperator(c_data["operator"]),
|
|
144
|
+
value=c_data["value"],
|
|
145
|
+
description=c_data.get("description"),
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
action_data = r_data.get("action", {})
|
|
150
|
+
action = Action(
|
|
151
|
+
type=ActionType(action_data.get("type", "warn")),
|
|
152
|
+
message=action_data.get("message"),
|
|
153
|
+
severity=SeverityLevel(action_data.get("severity", "error")),
|
|
154
|
+
remediation=action_data.get("remediation"),
|
|
155
|
+
auto_fix_command=action_data.get("auto_fix_command"),
|
|
156
|
+
notify_channels=action_data.get("notify_channels", []),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
rules.append(
|
|
160
|
+
Rule(
|
|
161
|
+
id=r_data["id"],
|
|
162
|
+
name=r_data["name"],
|
|
163
|
+
description=r_data.get("description", ""),
|
|
164
|
+
principle_id=r_data.get("principle_id", ""),
|
|
165
|
+
conditions=conditions,
|
|
166
|
+
action=action,
|
|
167
|
+
enabled=r_data.get("enabled", True),
|
|
168
|
+
severity=SeverityLevel(r_data.get("severity", "error")),
|
|
169
|
+
tags=r_data.get("tags", []),
|
|
170
|
+
environments=r_data.get("environments", []),
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Parse metrics
|
|
175
|
+
metrics = []
|
|
176
|
+
for m_data in data.get("metrics", []):
|
|
177
|
+
metrics.append(
|
|
178
|
+
Metric(
|
|
179
|
+
id=m_data["id"],
|
|
180
|
+
name=m_data["name"],
|
|
181
|
+
description=m_data.get("description", ""),
|
|
182
|
+
data_type=m_data.get("data_type", "number"),
|
|
183
|
+
aggregation=m_data.get("aggregation", "avg"),
|
|
184
|
+
warning_threshold=m_data.get("warning_threshold"),
|
|
185
|
+
critical_threshold=m_data.get("critical_threshold"),
|
|
186
|
+
target_threshold=m_data.get("target_threshold"),
|
|
187
|
+
unit=m_data.get("unit"),
|
|
188
|
+
dependencies=m_data.get("dependencies", []),
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# Parse thresholds
|
|
193
|
+
thresholds = []
|
|
194
|
+
for t_data in data.get("thresholds", []):
|
|
195
|
+
thresholds.append(
|
|
196
|
+
Threshold(
|
|
197
|
+
id=t_data["id"],
|
|
198
|
+
name=t_data["name"],
|
|
199
|
+
description=t_data.get("description", ""),
|
|
200
|
+
metric_id=t_data["metric_id"],
|
|
201
|
+
mode=ThresholdMode(t_data.get("mode", "absolute")),
|
|
202
|
+
value=t_data.get("value", 0),
|
|
203
|
+
operator=ConditionOperator(t_data.get("operator", "greater_than_or_equal")),
|
|
204
|
+
blocking=t_data.get("blocking", True),
|
|
205
|
+
environments=t_data.get("environments", []),
|
|
206
|
+
period=t_data.get("period"),
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
return Constitution(
|
|
211
|
+
name=data.get("name", "default"),
|
|
212
|
+
version=data.get("version", "1.0.0"),
|
|
213
|
+
description=data.get("description", ""),
|
|
214
|
+
principles=principles,
|
|
215
|
+
rules=rules,
|
|
216
|
+
metrics=metrics,
|
|
217
|
+
thresholds=thresholds,
|
|
218
|
+
extends=data.get("extends"),
|
|
219
|
+
metadata=data.get("metadata", {}),
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
def _merge(self, parent: Constitution, child: Constitution) -> Constitution:
|
|
223
|
+
"""Merge child constitution with parent."""
|
|
224
|
+
# Merge principles (child overrides parent)
|
|
225
|
+
principles = {p.id: p for p in parent.principles}
|
|
226
|
+
for p in child.principles:
|
|
227
|
+
principles[p.id] = p
|
|
228
|
+
|
|
229
|
+
# Merge rules
|
|
230
|
+
rules = {r.id: r for r in parent.rules}
|
|
231
|
+
for r in child.rules:
|
|
232
|
+
rules[r.id] = r
|
|
233
|
+
|
|
234
|
+
# Merge metrics
|
|
235
|
+
metrics = {m.id: m for m in parent.metrics}
|
|
236
|
+
for m in child.metrics:
|
|
237
|
+
metrics[m.id] = m
|
|
238
|
+
|
|
239
|
+
# Merge thresholds
|
|
240
|
+
thresholds = {t.id: t for t in parent.thresholds}
|
|
241
|
+
for t in child.thresholds:
|
|
242
|
+
thresholds[t.id] = t
|
|
243
|
+
|
|
244
|
+
return Constitution(
|
|
245
|
+
name=child.name,
|
|
246
|
+
version=child.version,
|
|
247
|
+
description=child.description or parent.description,
|
|
248
|
+
principles=list(principles.values()),
|
|
249
|
+
rules=list(rules.values()),
|
|
250
|
+
metrics=list(metrics.values()),
|
|
251
|
+
thresholds=list(thresholds.values()),
|
|
252
|
+
extends=None, # Already merged
|
|
253
|
+
metadata={**parent.metadata, **child.metadata},
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def load_constitution(path: str) -> Constitution:
|
|
258
|
+
"""Load a constitution from a file."""
|
|
259
|
+
loader = ConstitutionLoader()
|
|
260
|
+
return loader.load(path)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def get_default_constitution() -> Constitution:
|
|
264
|
+
"""Get the default constitution with standard rules."""
|
|
265
|
+
return Constitution(
|
|
266
|
+
name="default",
|
|
267
|
+
version="1.0.0",
|
|
268
|
+
description="Default quality constitution",
|
|
269
|
+
principles=[
|
|
270
|
+
Principle(
|
|
271
|
+
id="P001",
|
|
272
|
+
name="Code Quality",
|
|
273
|
+
description="Maintain high code quality standards",
|
|
274
|
+
priority=PriorityLevel.HIGH,
|
|
275
|
+
category="quality",
|
|
276
|
+
mandatory=True,
|
|
277
|
+
),
|
|
278
|
+
Principle(
|
|
279
|
+
id="P002",
|
|
280
|
+
name="Test Coverage",
|
|
281
|
+
description="Ensure adequate test coverage",
|
|
282
|
+
priority=PriorityLevel.HIGH,
|
|
283
|
+
category="testing",
|
|
284
|
+
mandatory=True,
|
|
285
|
+
),
|
|
286
|
+
Principle(
|
|
287
|
+
id="P003",
|
|
288
|
+
name="Security",
|
|
289
|
+
description="Follow security best practices",
|
|
290
|
+
priority=PriorityLevel.CRITICAL,
|
|
291
|
+
category="security",
|
|
292
|
+
mandatory=True,
|
|
293
|
+
),
|
|
294
|
+
Principle(
|
|
295
|
+
id="P004",
|
|
296
|
+
name="Performance",
|
|
297
|
+
description="Maintain acceptable performance",
|
|
298
|
+
priority=PriorityLevel.MEDIUM,
|
|
299
|
+
category="performance",
|
|
300
|
+
),
|
|
301
|
+
],
|
|
302
|
+
rules=[
|
|
303
|
+
Rule(
|
|
304
|
+
id="R001",
|
|
305
|
+
name="Minimum Test Coverage",
|
|
306
|
+
description="Code must have at least 80% test coverage",
|
|
307
|
+
principle_id="P002",
|
|
308
|
+
conditions=[
|
|
309
|
+
Condition(
|
|
310
|
+
field="coverage.percentage",
|
|
311
|
+
operator=ConditionOperator.GREATER_THAN_OR_EQUAL,
|
|
312
|
+
value=80,
|
|
313
|
+
)
|
|
314
|
+
],
|
|
315
|
+
action=Action(
|
|
316
|
+
type=ActionType.BLOCK,
|
|
317
|
+
message="Test coverage below 80%",
|
|
318
|
+
severity=SeverityLevel.ERROR,
|
|
319
|
+
remediation="Add more tests to increase coverage",
|
|
320
|
+
),
|
|
321
|
+
),
|
|
322
|
+
Rule(
|
|
323
|
+
id="R002",
|
|
324
|
+
name="No Critical Vulnerabilities",
|
|
325
|
+
description="No critical security vulnerabilities allowed",
|
|
326
|
+
principle_id="P003",
|
|
327
|
+
conditions=[
|
|
328
|
+
Condition(
|
|
329
|
+
field="security.critical_count",
|
|
330
|
+
operator=ConditionOperator.EQUALS,
|
|
331
|
+
value=0,
|
|
332
|
+
)
|
|
333
|
+
],
|
|
334
|
+
action=Action(
|
|
335
|
+
type=ActionType.BLOCK,
|
|
336
|
+
message="Critical vulnerabilities detected",
|
|
337
|
+
severity=SeverityLevel.ERROR,
|
|
338
|
+
remediation="Fix all critical vulnerabilities before deployment",
|
|
339
|
+
),
|
|
340
|
+
),
|
|
341
|
+
Rule(
|
|
342
|
+
id="R003",
|
|
343
|
+
name="Max Cyclomatic Complexity",
|
|
344
|
+
description="Functions should not exceed complexity threshold",
|
|
345
|
+
principle_id="P001",
|
|
346
|
+
conditions=[
|
|
347
|
+
Condition(
|
|
348
|
+
field="complexity.max_cyclomatic",
|
|
349
|
+
operator=ConditionOperator.LESS_THAN_OR_EQUAL,
|
|
350
|
+
value=15,
|
|
351
|
+
)
|
|
352
|
+
],
|
|
353
|
+
action=Action(
|
|
354
|
+
type=ActionType.WARN,
|
|
355
|
+
message="High cyclomatic complexity detected",
|
|
356
|
+
severity=SeverityLevel.WARNING,
|
|
357
|
+
remediation="Refactor complex functions",
|
|
358
|
+
),
|
|
359
|
+
),
|
|
360
|
+
Rule(
|
|
361
|
+
id="R004",
|
|
362
|
+
name="No Flaky Tests",
|
|
363
|
+
description="All tests must be stable",
|
|
364
|
+
principle_id="P002",
|
|
365
|
+
conditions=[
|
|
366
|
+
Condition(
|
|
367
|
+
field="tests.flaky_count",
|
|
368
|
+
operator=ConditionOperator.EQUALS,
|
|
369
|
+
value=0,
|
|
370
|
+
)
|
|
371
|
+
],
|
|
372
|
+
action=Action(
|
|
373
|
+
type=ActionType.WARN,
|
|
374
|
+
message="Flaky tests detected",
|
|
375
|
+
severity=SeverityLevel.WARNING,
|
|
376
|
+
remediation="Fix or quarantine flaky tests",
|
|
377
|
+
),
|
|
378
|
+
),
|
|
379
|
+
],
|
|
380
|
+
metrics=[
|
|
381
|
+
Metric(
|
|
382
|
+
id="M001",
|
|
383
|
+
name="Test Coverage",
|
|
384
|
+
description="Percentage of code covered by tests",
|
|
385
|
+
data_type="percentage",
|
|
386
|
+
warning_threshold=85,
|
|
387
|
+
critical_threshold=80,
|
|
388
|
+
target_threshold=90,
|
|
389
|
+
unit="%",
|
|
390
|
+
),
|
|
391
|
+
Metric(
|
|
392
|
+
id="M002",
|
|
393
|
+
name="Cyclomatic Complexity",
|
|
394
|
+
description="Average cyclomatic complexity",
|
|
395
|
+
data_type="number",
|
|
396
|
+
aggregation="avg",
|
|
397
|
+
warning_threshold=10,
|
|
398
|
+
critical_threshold=15,
|
|
399
|
+
),
|
|
400
|
+
Metric(
|
|
401
|
+
id="M003",
|
|
402
|
+
name="Security Vulnerabilities",
|
|
403
|
+
description="Count of security vulnerabilities",
|
|
404
|
+
data_type="count",
|
|
405
|
+
aggregation="sum",
|
|
406
|
+
warning_threshold=5,
|
|
407
|
+
critical_threshold=1,
|
|
408
|
+
),
|
|
409
|
+
],
|
|
410
|
+
thresholds=[
|
|
411
|
+
Threshold(
|
|
412
|
+
id="T001",
|
|
413
|
+
name="Coverage Gate",
|
|
414
|
+
description="Minimum test coverage for deployment",
|
|
415
|
+
metric_id="M001",
|
|
416
|
+
mode=ThresholdMode.ABSOLUTE,
|
|
417
|
+
value=80,
|
|
418
|
+
operator=ConditionOperator.GREATER_THAN_OR_EQUAL,
|
|
419
|
+
blocking=True,
|
|
420
|
+
),
|
|
421
|
+
Threshold(
|
|
422
|
+
id="T002",
|
|
423
|
+
name="Security Gate",
|
|
424
|
+
description="No critical vulnerabilities",
|
|
425
|
+
metric_id="M003",
|
|
426
|
+
mode=ThresholdMode.ABSOLUTE,
|
|
427
|
+
value=0,
|
|
428
|
+
operator=ConditionOperator.EQUALS,
|
|
429
|
+
blocking=True,
|
|
430
|
+
),
|
|
431
|
+
],
|
|
432
|
+
)
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Constitution Schema - Data models for quality rules and policies.
|
|
3
|
+
|
|
4
|
+
Defines the structure for:
|
|
5
|
+
- Principles: High-level quality guidance
|
|
6
|
+
- Rules: Enforcement mechanisms
|
|
7
|
+
- Metrics: Measurement definitions
|
|
8
|
+
- Thresholds: Quality gates
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from typing import Any, Dict, List, Optional
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PriorityLevel(str, Enum):
|
|
17
|
+
"""Priority levels for principles and rules."""
|
|
18
|
+
|
|
19
|
+
CRITICAL = "critical"
|
|
20
|
+
HIGH = "high"
|
|
21
|
+
MEDIUM = "medium"
|
|
22
|
+
LOW = "low"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ActionType(str, Enum):
|
|
26
|
+
"""Actions to take when a rule is violated."""
|
|
27
|
+
|
|
28
|
+
FAIL = "fail"
|
|
29
|
+
WARN = "warn"
|
|
30
|
+
NOTIFY = "notify"
|
|
31
|
+
BLOCK = "block"
|
|
32
|
+
REQUIRE_REVIEW = "require_review"
|
|
33
|
+
AUTO_FIX = "auto_fix"
|
|
34
|
+
ESCALATE = "escalate"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SeverityLevel(str, Enum):
|
|
38
|
+
"""Severity levels for violations."""
|
|
39
|
+
|
|
40
|
+
ERROR = "error"
|
|
41
|
+
WARNING = "warning"
|
|
42
|
+
INFO = "info"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ConditionOperator(str, Enum):
|
|
46
|
+
"""Operators for rule conditions."""
|
|
47
|
+
|
|
48
|
+
EQUALS = "equals"
|
|
49
|
+
NOT_EQUALS = "not_equals"
|
|
50
|
+
GREATER_THAN = "greater_than"
|
|
51
|
+
GREATER_THAN_OR_EQUAL = "greater_than_or_equal"
|
|
52
|
+
LESS_THAN = "less_than"
|
|
53
|
+
LESS_THAN_OR_EQUAL = "less_than_or_equal"
|
|
54
|
+
CONTAINS = "contains"
|
|
55
|
+
NOT_CONTAINS = "not_contains"
|
|
56
|
+
MATCHES = "matches"
|
|
57
|
+
IN = "in"
|
|
58
|
+
NOT_IN = "not_in"
|
|
59
|
+
EXISTS = "exists"
|
|
60
|
+
NOT_EXISTS = "not_exists"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ThresholdMode(str, Enum):
|
|
64
|
+
"""Mode for threshold evaluation."""
|
|
65
|
+
|
|
66
|
+
ABSOLUTE = "absolute"
|
|
67
|
+
PERCENTAGE = "percentage"
|
|
68
|
+
RELATIVE = "relative"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class Condition:
|
|
73
|
+
"""A condition for rule evaluation."""
|
|
74
|
+
|
|
75
|
+
field: str
|
|
76
|
+
operator: ConditionOperator
|
|
77
|
+
value: Any
|
|
78
|
+
description: Optional[str] = None
|
|
79
|
+
|
|
80
|
+
def evaluate(self, context: Dict[str, Any]) -> bool:
|
|
81
|
+
"""Evaluate the condition against a context."""
|
|
82
|
+
actual = self._get_field_value(context, self.field)
|
|
83
|
+
|
|
84
|
+
if self.operator == ConditionOperator.EQUALS:
|
|
85
|
+
return actual == self.value
|
|
86
|
+
elif self.operator == ConditionOperator.NOT_EQUALS:
|
|
87
|
+
return actual != self.value
|
|
88
|
+
elif self.operator == ConditionOperator.GREATER_THAN:
|
|
89
|
+
return actual > self.value
|
|
90
|
+
elif self.operator == ConditionOperator.GREATER_THAN_OR_EQUAL:
|
|
91
|
+
return actual >= self.value
|
|
92
|
+
elif self.operator == ConditionOperator.LESS_THAN:
|
|
93
|
+
return actual < self.value
|
|
94
|
+
elif self.operator == ConditionOperator.LESS_THAN_OR_EQUAL:
|
|
95
|
+
return actual <= self.value
|
|
96
|
+
elif self.operator == ConditionOperator.CONTAINS:
|
|
97
|
+
return self.value in actual if actual else False
|
|
98
|
+
elif self.operator == ConditionOperator.NOT_CONTAINS:
|
|
99
|
+
return self.value not in actual if actual else True
|
|
100
|
+
elif self.operator == ConditionOperator.MATCHES:
|
|
101
|
+
import re
|
|
102
|
+
|
|
103
|
+
return bool(re.match(self.value, str(actual)))
|
|
104
|
+
elif self.operator == ConditionOperator.IN:
|
|
105
|
+
return actual in self.value
|
|
106
|
+
elif self.operator == ConditionOperator.NOT_IN:
|
|
107
|
+
return actual not in self.value
|
|
108
|
+
elif self.operator == ConditionOperator.EXISTS:
|
|
109
|
+
return actual is not None
|
|
110
|
+
elif self.operator == ConditionOperator.NOT_EXISTS:
|
|
111
|
+
return actual is None
|
|
112
|
+
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
def _get_field_value(self, context: Dict[str, Any], field: str) -> Any:
|
|
116
|
+
"""Get a field value from context using dot notation."""
|
|
117
|
+
parts = field.split(".")
|
|
118
|
+
value = context
|
|
119
|
+
for part in parts:
|
|
120
|
+
if isinstance(value, dict):
|
|
121
|
+
value = value.get(part)
|
|
122
|
+
else:
|
|
123
|
+
return None
|
|
124
|
+
return value
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@dataclass
|
|
128
|
+
class Action:
|
|
129
|
+
"""An action to take when a rule is violated."""
|
|
130
|
+
|
|
131
|
+
type: ActionType
|
|
132
|
+
message: Optional[str] = None
|
|
133
|
+
severity: SeverityLevel = SeverityLevel.ERROR
|
|
134
|
+
remediation: Optional[str] = None
|
|
135
|
+
auto_fix_command: Optional[str] = None
|
|
136
|
+
notify_channels: List[str] = field(default_factory=list)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass
|
|
140
|
+
class Principle:
|
|
141
|
+
"""A quality principle - high-level guidance."""
|
|
142
|
+
|
|
143
|
+
id: str
|
|
144
|
+
name: str
|
|
145
|
+
description: str
|
|
146
|
+
priority: PriorityLevel = PriorityLevel.MEDIUM
|
|
147
|
+
category: str = "general"
|
|
148
|
+
mandatory: bool = False
|
|
149
|
+
related_principles: List[str] = field(default_factory=list)
|
|
150
|
+
tags: List[str] = field(default_factory=list)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@dataclass
|
|
154
|
+
class Rule:
|
|
155
|
+
"""A rule for enforcing a principle."""
|
|
156
|
+
|
|
157
|
+
id: str
|
|
158
|
+
name: str
|
|
159
|
+
description: str
|
|
160
|
+
principle_id: str
|
|
161
|
+
conditions: List[Condition]
|
|
162
|
+
action: Action
|
|
163
|
+
enabled: bool = True
|
|
164
|
+
severity: SeverityLevel = SeverityLevel.ERROR
|
|
165
|
+
tags: List[str] = field(default_factory=list)
|
|
166
|
+
environments: List[str] = field(default_factory=list) # Which environments to apply
|
|
167
|
+
|
|
168
|
+
def evaluate(self, context: Dict[str, Any]) -> bool:
|
|
169
|
+
"""Evaluate all conditions. Returns True if rule passes (no violation)."""
|
|
170
|
+
return all(cond.evaluate(context) for cond in self.conditions)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@dataclass
|
|
174
|
+
class Metric:
|
|
175
|
+
"""A metric definition for measurement."""
|
|
176
|
+
|
|
177
|
+
id: str
|
|
178
|
+
name: str
|
|
179
|
+
description: str
|
|
180
|
+
data_type: str # number, percentage, duration, count, ratio
|
|
181
|
+
aggregation: str = "avg" # sum, avg, min, max, count, percentile
|
|
182
|
+
warning_threshold: Optional[float] = None
|
|
183
|
+
critical_threshold: Optional[float] = None
|
|
184
|
+
target_threshold: Optional[float] = None
|
|
185
|
+
unit: Optional[str] = None
|
|
186
|
+
dependencies: List[str] = field(default_factory=list)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@dataclass
|
|
190
|
+
class Threshold:
|
|
191
|
+
"""A threshold/quality gate definition."""
|
|
192
|
+
|
|
193
|
+
id: str
|
|
194
|
+
name: str
|
|
195
|
+
description: str
|
|
196
|
+
metric_id: str
|
|
197
|
+
mode: ThresholdMode = ThresholdMode.ABSOLUTE
|
|
198
|
+
value: float = 0.0
|
|
199
|
+
operator: ConditionOperator = ConditionOperator.GREATER_THAN_OR_EQUAL
|
|
200
|
+
blocking: bool = True # If True, failing blocks deployment
|
|
201
|
+
environments: List[str] = field(default_factory=list)
|
|
202
|
+
period: Optional[str] = None # e.g., "7d" for last 7 days
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@dataclass
|
|
206
|
+
class Constitution:
|
|
207
|
+
"""
|
|
208
|
+
A complete constitution defining quality rules and policies.
|
|
209
|
+
|
|
210
|
+
Contains principles, rules, metrics, and thresholds that
|
|
211
|
+
define acceptable quality standards for a project.
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
name: str
|
|
215
|
+
version: str
|
|
216
|
+
description: str = ""
|
|
217
|
+
principles: List[Principle] = field(default_factory=list)
|
|
218
|
+
rules: List[Rule] = field(default_factory=list)
|
|
219
|
+
metrics: List[Metric] = field(default_factory=list)
|
|
220
|
+
thresholds: List[Threshold] = field(default_factory=list)
|
|
221
|
+
extends: Optional[str] = None # Parent constitution to inherit from
|
|
222
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
223
|
+
|
|
224
|
+
def get_principle(self, id: str) -> Optional[Principle]:
|
|
225
|
+
"""Get a principle by ID."""
|
|
226
|
+
return next((p for p in self.principles if p.id == id), None)
|
|
227
|
+
|
|
228
|
+
def get_rule(self, id: str) -> Optional[Rule]:
|
|
229
|
+
"""Get a rule by ID."""
|
|
230
|
+
return next((r for r in self.rules if r.id == id), None)
|
|
231
|
+
|
|
232
|
+
def get_metric(self, id: str) -> Optional[Metric]:
|
|
233
|
+
"""Get a metric by ID."""
|
|
234
|
+
return next((m for m in self.metrics if m.id == id), None)
|
|
235
|
+
|
|
236
|
+
def get_threshold(self, id: str) -> Optional[Threshold]:
|
|
237
|
+
"""Get a threshold by ID."""
|
|
238
|
+
return next((t for t in self.thresholds if t.id == id), None)
|
|
239
|
+
|
|
240
|
+
def get_rules_for_principle(self, principle_id: str) -> List[Rule]:
|
|
241
|
+
"""Get all rules for a principle."""
|
|
242
|
+
return [r for r in self.rules if r.principle_id == principle_id]
|
|
243
|
+
|
|
244
|
+
def get_enabled_rules(self) -> List[Rule]:
|
|
245
|
+
"""Get all enabled rules."""
|
|
246
|
+
return [r for r in self.rules if r.enabled]
|
|
247
|
+
|
|
248
|
+
def get_blocking_thresholds(self) -> List[Threshold]:
|
|
249
|
+
"""Get all blocking thresholds."""
|
|
250
|
+
return [t for t in self.thresholds if t.blocking]
|