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,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]