devsquad 3.6.0__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 (95) hide show
  1. devsquad-3.6.0.dist-info/METADATA +944 -0
  2. devsquad-3.6.0.dist-info/RECORD +95 -0
  3. devsquad-3.6.0.dist-info/WHEEL +5 -0
  4. devsquad-3.6.0.dist-info/entry_points.txt +2 -0
  5. devsquad-3.6.0.dist-info/licenses/LICENSE +21 -0
  6. devsquad-3.6.0.dist-info/top_level.txt +2 -0
  7. scripts/__init__.py +0 -0
  8. scripts/ai_semantic_matcher.py +512 -0
  9. scripts/alert_manager.py +505 -0
  10. scripts/api/__init__.py +43 -0
  11. scripts/api/models.py +386 -0
  12. scripts/api/routes/__init__.py +20 -0
  13. scripts/api/routes/dispatch.py +348 -0
  14. scripts/api/routes/lifecycle.py +330 -0
  15. scripts/api/routes/metrics_gates.py +347 -0
  16. scripts/api_server.py +318 -0
  17. scripts/auth.py +451 -0
  18. scripts/cli/__init__.py +1 -0
  19. scripts/cli/cli_visual.py +642 -0
  20. scripts/cli.py +1094 -0
  21. scripts/collaboration/__init__.py +212 -0
  22. scripts/collaboration/_version.py +1 -0
  23. scripts/collaboration/agent_briefing.py +656 -0
  24. scripts/collaboration/ai_semantic_matcher.py +260 -0
  25. scripts/collaboration/anchor_checker.py +281 -0
  26. scripts/collaboration/anti_rationalization.py +470 -0
  27. scripts/collaboration/async_integration_example.py +255 -0
  28. scripts/collaboration/batch_scheduler.py +149 -0
  29. scripts/collaboration/checkpoint_manager.py +561 -0
  30. scripts/collaboration/ci_feedback_adapter.py +351 -0
  31. scripts/collaboration/code_map_generator.py +247 -0
  32. scripts/collaboration/concern_pack_loader.py +352 -0
  33. scripts/collaboration/confidence_score.py +496 -0
  34. scripts/collaboration/config_loader.py +188 -0
  35. scripts/collaboration/consensus.py +244 -0
  36. scripts/collaboration/context_compressor.py +533 -0
  37. scripts/collaboration/coordinator.py +668 -0
  38. scripts/collaboration/dispatcher.py +1636 -0
  39. scripts/collaboration/dual_layer_context.py +128 -0
  40. scripts/collaboration/enhanced_worker.py +539 -0
  41. scripts/collaboration/feature_usage_tracker.py +206 -0
  42. scripts/collaboration/five_axis_consensus.py +334 -0
  43. scripts/collaboration/input_validator.py +401 -0
  44. scripts/collaboration/integration_example.py +287 -0
  45. scripts/collaboration/intent_workflow_mapper.py +350 -0
  46. scripts/collaboration/language_parsers.py +269 -0
  47. scripts/collaboration/lifecycle_protocol.py +1446 -0
  48. scripts/collaboration/llm_backend.py +453 -0
  49. scripts/collaboration/llm_cache.py +448 -0
  50. scripts/collaboration/llm_cache_async.py +347 -0
  51. scripts/collaboration/llm_retry.py +387 -0
  52. scripts/collaboration/llm_retry_async.py +389 -0
  53. scripts/collaboration/mce_adapter.py +597 -0
  54. scripts/collaboration/memory_bridge.py +1607 -0
  55. scripts/collaboration/models.py +537 -0
  56. scripts/collaboration/null_providers.py +297 -0
  57. scripts/collaboration/operation_classifier.py +289 -0
  58. scripts/collaboration/output_slicer.py +225 -0
  59. scripts/collaboration/performance_monitor.py +462 -0
  60. scripts/collaboration/permission_guard.py +865 -0
  61. scripts/collaboration/prompt_assembler.py +756 -0
  62. scripts/collaboration/prompt_variant_generator.py +483 -0
  63. scripts/collaboration/protocols.py +267 -0
  64. scripts/collaboration/report_formatter.py +352 -0
  65. scripts/collaboration/retrospective.py +279 -0
  66. scripts/collaboration/role_matcher.py +92 -0
  67. scripts/collaboration/role_template_market.py +352 -0
  68. scripts/collaboration/rule_collector.py +678 -0
  69. scripts/collaboration/scratchpad.py +346 -0
  70. scripts/collaboration/skill_registry.py +151 -0
  71. scripts/collaboration/skillifier.py +878 -0
  72. scripts/collaboration/standardized_role_template.py +317 -0
  73. scripts/collaboration/task_completion_checker.py +237 -0
  74. scripts/collaboration/test_quality_guard.py +695 -0
  75. scripts/collaboration/unified_gate_engine.py +598 -0
  76. scripts/collaboration/usage_tracker.py +309 -0
  77. scripts/collaboration/user_friendly_error.py +176 -0
  78. scripts/collaboration/verification_gate.py +312 -0
  79. scripts/collaboration/warmup_manager.py +635 -0
  80. scripts/collaboration/worker.py +513 -0
  81. scripts/collaboration/workflow_engine.py +684 -0
  82. scripts/dashboard.py +1088 -0
  83. scripts/generate_benchmark_report.py +786 -0
  84. scripts/history_manager.py +604 -0
  85. scripts/mcp_server.py +289 -0
  86. skills/__init__.py +32 -0
  87. skills/dispatch/handler.py +52 -0
  88. skills/intent/handler.py +59 -0
  89. skills/registry.py +67 -0
  90. skills/retrospective/__init__.py +0 -0
  91. skills/retrospective/handler.py +125 -0
  92. skills/review/handler.py +356 -0
  93. skills/security/handler.py +454 -0
  94. skills/test/__init__.py +0 -0
  95. skills/test/handler.py +78 -0
@@ -0,0 +1,297 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ DevSquad Null Providers
5
+
6
+ Provides no-op implementations for all Protocol interfaces, used for:
7
+ - Degradation: auto-switch when real Provider is unavailable
8
+ - Test mocking: quick tests without real dependencies
9
+ - Development: skip certain modules to focus on core logic
10
+
11
+ Characteristics:
12
+ - All methods succeed silently, never raise exceptions
13
+ - is_available() returns False (marks as degraded implementation)
14
+ - No actual operations performed (no side effects)
15
+
16
+ Version: v1.0
17
+ Created: 2026-05-01
18
+ """
19
+
20
+ from typing import Optional, Dict, Any, List, Callable
21
+ import logging
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ class NullCacheProvider:
27
+ """
28
+ No-op cache implementation.
29
+
30
+ Behavior:
31
+ - get() always returns None (cache miss)
32
+ - set() succeeds silently, no actual storage
33
+ - clear() succeeds silently
34
+ - is_available() returns False
35
+ """
36
+
37
+ def __init__(self):
38
+ self._call_count = 0
39
+ logger.info("NullCacheProvider initialized (degraded mode)")
40
+
41
+ def get(self, prompt: str, backend: str, model: str) -> Optional[str]:
42
+ """Retrieve cached response (always returns None)."""
43
+ self._call_count += 1
44
+ logger.debug("NullCacheProvider.get() called (miss) - call #%d", self._call_count)
45
+ return None
46
+
47
+ def set(self, prompt: str, response: str, backend: str, model: str, ttl: Optional[int] = None) -> None:
48
+ """Store response in cache (no-op)."""
49
+ self._call_count += 1
50
+ logger.debug("NullCacheProvider.set() called (no-op) - call #%d", self._call_count)
51
+
52
+ def clear(self) -> None:
53
+ """Clear cache (no-op)."""
54
+ logger.debug("NullCacheProvider.clear() called (no-op)")
55
+
56
+ def is_available(self) -> bool:
57
+ """Check if cache is available. Returns False (degraded)."""
58
+ return False
59
+
60
+ def get_stats(self) -> Dict[str, Any]:
61
+ """Return empty cache statistics."""
62
+ return {
63
+ "hit_count": 0,
64
+ "miss_count": self._call_count,
65
+ "hit_rate": 0.0,
66
+ "total_size": 0,
67
+ "entry_count": 0,
68
+ "provider_type": "null",
69
+ "degraded": True
70
+ }
71
+
72
+
73
+ class NullRetryProvider:
74
+ """
75
+ No-op retry implementation.
76
+
77
+ Behavior:
78
+ - retry_with_fallback() executes function directly, no retry
79
+ - On failure, calls fallback if provided
80
+ - is_available() returns False
81
+ """
82
+
83
+ def __init__(self):
84
+ self._call_count = 0
85
+ self._success_count = 0
86
+ self._failure_count = 0
87
+ self._fallback_count = 0
88
+ logger.info("NullRetryProvider initialized (degraded mode)")
89
+
90
+ def retry_with_fallback(
91
+ self,
92
+ func: Callable[[], Any],
93
+ max_attempts: int = 3,
94
+ fallback: Optional[Callable[[], Any]] = None
95
+ ) -> Any:
96
+ """Execute function without retry. Falls back on failure."""
97
+ self._call_count += 1
98
+
99
+ try:
100
+ result = func()
101
+ self._success_count += 1
102
+ logger.debug("NullRetryProvider: function succeeded (no retry) - call #%d", self._call_count)
103
+ return result
104
+ except Exception as e:
105
+ self._failure_count += 1
106
+ logger.debug("NullRetryProvider: function failed (no retry) - call #%d: %s", self._call_count, e)
107
+
108
+ if fallback:
109
+ self._fallback_count += 1
110
+ logger.debug("NullRetryProvider: calling fallback - call #%d", self._call_count)
111
+ return fallback()
112
+ else:
113
+ raise
114
+
115
+ def is_available(self) -> bool:
116
+ """Check if retry mechanism is available. Returns False (degraded)."""
117
+ return False
118
+
119
+ def get_stats(self) -> Dict[str, Any]:
120
+ """Return retry statistics."""
121
+ return {
122
+ "total_attempts": self._call_count,
123
+ "success_count": self._success_count,
124
+ "failure_count": self._failure_count,
125
+ "fallback_count": self._fallback_count,
126
+ "avg_attempts": 1.0,
127
+ "provider_type": "null",
128
+ "degraded": True
129
+ }
130
+
131
+
132
+ class NullMonitorProvider:
133
+ """
134
+ No-op monitoring implementation.
135
+
136
+ Behavior:
137
+ - record_*() succeeds silently, no actual recording
138
+ - generate_report() writes empty report
139
+ - is_available() returns False
140
+ """
141
+
142
+ def __init__(self):
143
+ self._llm_call_count = 0
144
+ self._agent_execution_count = 0
145
+ logger.info("NullMonitorProvider initialized (degraded mode)")
146
+
147
+ def record_llm_call(
148
+ self,
149
+ backend: str,
150
+ model: str,
151
+ duration: float,
152
+ token_count: int,
153
+ success: bool,
154
+ metadata: Optional[Dict[str, Any]] = None
155
+ ) -> None:
156
+ """Record LLM call (no-op)."""
157
+ self._llm_call_count += 1
158
+ logger.debug("NullMonitorProvider.record_llm_call() called (no-op) - call #%d", self._llm_call_count)
159
+
160
+ def record_agent_execution(
161
+ self,
162
+ agent_role: str,
163
+ task: str,
164
+ duration: float,
165
+ success: bool,
166
+ metadata: Optional[Dict[str, Any]] = None
167
+ ) -> None:
168
+ """Record agent execution (no-op)."""
169
+ self._agent_execution_count += 1
170
+ logger.debug("NullMonitorProvider.record_agent_execution() called (no-op) - call #%d", self._agent_execution_count)
171
+
172
+ def generate_report(self, output_path: str) -> None:
173
+ """Generate empty performance report."""
174
+ logger.debug("NullMonitorProvider.generate_report() called (empty report) - path: %s", output_path)
175
+ try:
176
+ with open(output_path, "w") as f:
177
+ f.write("# Performance Report (Degraded Mode)\n\n")
178
+ f.write("Monitoring is currently unavailable (NullMonitorProvider).\n")
179
+ f.write("No performance data was collected.\n")
180
+ except Exception as e:
181
+ logger.warning("NullMonitorProvider: failed to write empty report: %s", e)
182
+
183
+ def is_available(self) -> bool:
184
+ """Check if monitoring is available. Returns False (degraded)."""
185
+ return False
186
+
187
+ def get_stats(self) -> Dict[str, Any]:
188
+ """Return empty monitoring statistics."""
189
+ return {
190
+ "total_llm_calls": 0,
191
+ "total_agent_executions": 0,
192
+ "avg_llm_duration": 0.0,
193
+ "avg_agent_duration": 0.0,
194
+ "total_tokens": 0,
195
+ "provider_type": "null",
196
+ "degraded": True
197
+ }
198
+
199
+
200
+ class NullMemoryProvider:
201
+ """
202
+ No-op memory implementation.
203
+
204
+ Behavior:
205
+ - get_rules() returns empty list
206
+ - add_rule() / update_rule() / delete_rule() succeed silently
207
+ - is_available() returns False
208
+ """
209
+
210
+ def __init__(self):
211
+ self._call_count = 0
212
+ logger.info("NullMemoryProvider initialized (degraded mode)")
213
+
214
+ def get_rules(self, user_id: str, context: Optional[Dict[str, Any]] = None) -> List[str]:
215
+ """Retrieve user rules (always returns empty list)."""
216
+ self._call_count += 1
217
+ logger.debug("NullMemoryProvider.get_rules() called (empty) - user: %s, call #%d", user_id, self._call_count)
218
+ return []
219
+
220
+ def add_rule(self, user_id: str, rule: str, metadata: Optional[Dict[str, Any]] = None) -> None:
221
+ """Add user rule (no-op)."""
222
+ self._call_count += 1
223
+ logger.debug("NullMemoryProvider.add_rule() called (no-op) - user: %s, call #%d", user_id, self._call_count)
224
+
225
+ def update_rule(self, user_id: str, rule_id: str, rule: str) -> None:
226
+ """Update user rule (no-op)."""
227
+ self._call_count += 1
228
+ logger.debug("NullMemoryProvider.update_rule() called (no-op) - user: %s, rule: %s, call #%d", user_id, rule_id, self._call_count)
229
+
230
+ def delete_rule(self, user_id: str, rule_id: str) -> None:
231
+ """Delete user rule (no-op)."""
232
+ self._call_count += 1
233
+ logger.debug("NullMemoryProvider.delete_rule() called (no-op) - user: %s, rule: %s, call #%d", user_id, rule_id, self._call_count)
234
+
235
+ def is_available(self) -> bool:
236
+ """Check if memory system is available. Returns False (degraded)."""
237
+ return False
238
+
239
+ def get_stats(self) -> Dict[str, Any]:
240
+ """Return empty memory statistics."""
241
+ return {
242
+ "total_users": 0,
243
+ "total_rules": 0,
244
+ "avg_rules_per_user": 0.0,
245
+ "provider_type": "null",
246
+ "degraded": True
247
+ }
248
+
249
+ def match_rules(self, task_description: str, user_id: str,
250
+ role: Optional[str] = None, max_rules: int = 5) -> List[Dict[str, Any]]:
251
+ """Match rules based on task description (always returns empty list)."""
252
+ self._call_count += 1
253
+ logger.debug("NullMemoryProvider.match_rules() called (empty) - user: %s, call #%d", user_id, self._call_count)
254
+ return []
255
+
256
+ def format_rules_as_prompt(self, rules: List[Dict[str, Any]]) -> str:
257
+ """Format rules as prompt text (always returns empty string)."""
258
+ self._call_count += 1
259
+ logger.debug("NullMemoryProvider.format_rules_as_prompt() called (empty) - call #%d", self._call_count)
260
+ return ""
261
+
262
+
263
+ # ============================================================================
264
+ # Factory functions
265
+ # ============================================================================
266
+
267
+ def get_null_cache() -> NullCacheProvider:
268
+ """Get a null cache instance."""
269
+ return NullCacheProvider()
270
+
271
+
272
+ def get_null_retry() -> NullRetryProvider:
273
+ """Get a null retry instance."""
274
+ return NullRetryProvider()
275
+
276
+
277
+ def get_null_monitor() -> NullMonitorProvider:
278
+ """Get a null monitor instance."""
279
+ return NullMonitorProvider()
280
+
281
+
282
+ def get_null_memory() -> NullMemoryProvider:
283
+ """Get a null memory instance."""
284
+ return NullMemoryProvider()
285
+
286
+
287
+ __version__ = "1.0.0"
288
+ __all__ = [
289
+ "NullCacheProvider",
290
+ "NullRetryProvider",
291
+ "NullMonitorProvider",
292
+ "NullMemoryProvider",
293
+ "get_null_cache",
294
+ "get_null_retry",
295
+ "get_null_monitor",
296
+ "get_null_memory",
297
+ ]
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ OperationCategory Extension for PermissionGuard (P1-2)
5
+
6
+ Adds three-tier operation classification to existing 4-level permission model:
7
+ - ALWAYS_SAFE: Read-only, local queries (auto-approved at most levels)
8
+ - NEEDS_REVIEW: Write ops, external API calls (requires confirmation or AI check)
9
+ - FORBIDDEN: Dangerous ops (delete, secrets, eval) (denied unless BYPASS)
10
+
11
+ Spec reference: SPEC_V35_Agent_Skills_Quality_Framework.md Section 7.2
12
+ """
13
+
14
+ from dataclasses import dataclass, field
15
+ from datetime import datetime
16
+ from enum import Enum
17
+ from typing import Any, Dict, List, Optional, Tuple
18
+
19
+
20
+ class OperationCategory(Enum):
21
+ """
22
+ Three-tier operation classification for fine-grained permission control.
23
+
24
+ Hierarchy:
25
+ ALWAYS_SAFE → Auto-approved at DEFAULT/AUTO levels
26
+ NEEDS_REVIEW → Requires explicit approval or AI risk assessment
27
+ FORBIDDEN → Blocked unless BYPASS level + explicit override
28
+ """
29
+ ALWAYS_SAFE = "always_safe"
30
+ NEEDS_REVIEW = "needs_review"
31
+ FORBIDDEN = "forbidden"
32
+
33
+
34
+ # Default classification mapping for common operations
35
+ OPERATION_CLASSIFICATION: Dict[str, OperationCategory] = {
36
+ # === Always Safe Operations ===
37
+ "read_config": OperationCategory.ALWAYS_SAFE,
38
+ "read_file": OperationCategory.ALWAYS_SAFE,
39
+ "read_scratchpad": OperationCategory.ALWAYS_SAFE,
40
+ "list_directory": OperationCategory.ALWAYS_SAFE,
41
+ "query_status": OperationCategory.ALWAYS_SAFE,
42
+ "get_role_info": OperationCategory.ALWAYS_SAFE,
43
+ "validate_input": OperationCategory.ALWAYS_SAFE,
44
+
45
+ # === Needs Review Operations ===
46
+ "write_scratchpad": OperationCategory.NEEDS_REVIEW,
47
+ "write_file": OperationCategory.NEEDS_REVIEW,
48
+ "create_file": OperationCategory.NEEDS_REVIEW,
49
+ "modify_file": OperationCategory.NEEDS_REVIEW,
50
+ "call_llm": OperationCategory.NEEDS_REVIEW,
51
+ "network_request": OperationCategory.NEEDS_REVIEW,
52
+ "git_operation": OperationCategory.NEEDS_REVIEW,
53
+ "modify_config": OperationCategory.NEEDS_REVIEW,
54
+ "install_template": OperationCategory.NEEDS_REVIEW,
55
+ "publish_template": OperationCategory.NEEDS_REVIEW,
56
+
57
+ # === Forbidden Operations ===
58
+ "delete_file": OperationCategory.FORBIDDEN,
59
+ "execute_shell": OperationCategory.FORBIDDEN,
60
+ "access_secrets": OperationCategory.FORBIDDEN,
61
+ "eval_code": OperationCategory.FORBIDDEN,
62
+ "import_module": OperationCategory.FORBIDDEN,
63
+ "spawn_process": OperationCategory.FORBIDDEN,
64
+ "modify_system_path": OperationCategory.FORBIDDEN,
65
+ "environment_write": OperationCategory.FORBIDDEN,
66
+ }
67
+
68
+
69
+ @dataclass
70
+ class ClassifiedOperation:
71
+ """An operation with its category classification."""
72
+ operation_id: str
73
+ category: OperationCategory
74
+ description: str
75
+ risk_factors: List[str]
76
+ requires_confirmation: bool
77
+ override_allowed: bool
78
+
79
+ def to_dict(self) -> Dict[str, Any]:
80
+ return {
81
+ "operation_id": self.operation_id,
82
+ "category": self.category.value,
83
+ "description": self.description,
84
+ "risk_factors": self.risk_factors,
85
+ "requires_confirmation": self.requires_confirmation,
86
+ "override_allowed": self.override_allowed,
87
+ }
88
+
89
+
90
+ class OperationClassifier:
91
+ """
92
+ Classifies operations into three-tier categories.
93
+
94
+ Usage:
95
+ classifier = OperationClassifier()
96
+ classified = classifier.classify("delete_file", "/tmp/important.txt")
97
+ if classified.category == OperationCategory.FORBIDDEN:
98
+ # Block or escalate
99
+ pass
100
+ """
101
+
102
+ def __init__(
103
+ self,
104
+ custom_classifications: Optional[Dict[str, OperationCategory]] = None,
105
+ strict_mode: bool = False,
106
+ ):
107
+ """
108
+ Initialize classifier.
109
+
110
+ Args:
111
+ custom_classifications: Override default classifications
112
+ strict_mode: If True, unknown operations are classified as FORBIDDEN
113
+ If False (default), unknown operations are NEEDS_REVIEW
114
+ """
115
+ self._classifications = dict(OPERATION_CLASSIFICATION)
116
+ if custom_classifications:
117
+ self._classifications.update(custom_classifications)
118
+ self._strict_mode = strict_mode
119
+
120
+ def classify(
121
+ self,
122
+ operation_id: str,
123
+ target: Optional[str] = None,
124
+ context: Optional[Dict[str, Any]] = None,
125
+ ) -> ClassifiedOperation:
126
+ """
127
+ Classify an operation into a category.
128
+
129
+ Args:
130
+ operation_id: The operation identifier
131
+ target: Optional target path/URL for context
132
+ context: Additional context (source role, etc.)
133
+
134
+ Returns:
135
+ ClassifiedOperation with full details
136
+ """
137
+ base_category = self._classifications.get(operation_id)
138
+
139
+ if base_category is None:
140
+ if self._strict_mode:
141
+ base_category = OperationCategory.FORBIDDEN
142
+ else:
143
+ base_category = OperationCategory.NEEDS_REVIEW
144
+
145
+ description = self._get_description(operation_id)
146
+ risk_factors = self._assess_risk_factors(operation_id, target, context)
147
+
148
+ return ClassifiedOperation(
149
+ operation_id=operation_id,
150
+ category=base_category,
151
+ description=description,
152
+ risk_factors=risk_factors,
153
+ requires_confirmation=(
154
+ base_category == OperationCategory.NEEDS_REVIEW or
155
+ base_category == OperationCategory.FORBIDDEN
156
+ ),
157
+ override_allowed=base_category != OperationCategory.FORBIDDEN,
158
+ )
159
+
160
+ def batch_classify(
161
+ self,
162
+ operations: List[Dict[str, Any]],
163
+ ) -> List[ClassifiedOperation]:
164
+ """
165
+ Classify multiple operations at once.
166
+
167
+ Args:
168
+ operations: List of dicts with 'operation_id' and optional 'target', 'context'
169
+
170
+ Returns:
171
+ List of ClassifiedOperation results
172
+ """
173
+ return [
174
+ self.classify(
175
+ op.get('operation_id', ''),
176
+ op.get('target'),
177
+ op.get('context'),
178
+ )
179
+ for op in operations
180
+ ]
181
+
182
+ def is_allowed(
183
+ self,
184
+ operation_id: str,
185
+ permission_level: str = "DEFAULT",
186
+ target: Optional[str] = None,
187
+ ) -> tuple:
188
+ """
189
+ Quick check if operation is allowed at given permission level.
190
+
191
+ Returns:
192
+ (allowed: bool, reason: str)
193
+ """
194
+ classified = self.classify(operation_id, target)
195
+
196
+ if classified.category == OperationCategory.ALWAYS_SAFE:
197
+ return True, "Operation is always safe"
198
+
199
+ if classified.category == OperationCategory.FORBIDDEN:
200
+ if permission_level.upper() == "BYPASS":
201
+ return True, "Allowed via BYPASS override"
202
+ return False, f"Operation '{operation_id}' is forbidden"
203
+
204
+ if classified.category == OperationCategory.NEEDS_REVIEW:
205
+ if permission_level.upper() in ("AUTO", "BYPASS"):
206
+ return True, f"Auto-approved at {permission_level} level"
207
+ if permission_level.upper() == "PLAN":
208
+ return False, "Write operations denied in PLAN mode"
209
+ return True, "Requires user confirmation"
210
+
211
+ return False, "Unknown category"
212
+
213
+ def get_forbidden_operations(self) -> List[str]:
214
+ """Return list of all operations classified as FORBIDDEN."""
215
+ return [
216
+ op_id
217
+ for op_id, cat in self._classifications.items()
218
+ if cat == OperationCategory.FORBIDDEN
219
+ ]
220
+
221
+ def get_review_required_operations(self) -> List[str]:
222
+ """Return list of all operations classified as NEEDS_REVIEW."""
223
+ return [
224
+ op_id
225
+ for op_id, cat in self._classifications.items()
226
+ if cat == OperationCategory.NEEDS_REVIEW
227
+ ]
228
+
229
+ def add_custom_classification(
230
+ self,
231
+ operation_id: str,
232
+ category: OperationCategory,
233
+ ):
234
+ """Add or update custom operation classification."""
235
+ self._classifications[operation_id] = category
236
+
237
+ def _get_description(self, operation_id: str) -> str:
238
+ descriptions = {
239
+ "read_config": "Read configuration values",
240
+ "write_file": "Write or modify file contents",
241
+ "delete_file": "Delete file from filesystem",
242
+ "execute_shell": "Execute shell command",
243
+ "call_llm": "Call LLM API for inference",
244
+ "access_secrets": "Access secret keys or credentials",
245
+ "eval_code": "Evaluate arbitrary code string",
246
+ "read_scratchpad": "Read shared scratchpad data",
247
+ "write_scratchpad": "Write to shared scratchpad",
248
+ }
249
+ return descriptions.get(operation_id, f"Operation: {operation_id}")
250
+
251
+ def _assess_risk_factors(
252
+ self,
253
+ operation_id: str,
254
+ target: Optional[str],
255
+ context: Optional[Dict[str, Any]],
256
+ ) -> List[str]:
257
+ factors = []
258
+ category = self._classifications.get(operation_id, OperationCategory.NEEDS_REVIEW)
259
+
260
+ if category == OperationCategory.FORBIDDEN:
261
+ factors.append("High-risk operation category")
262
+
263
+ if target:
264
+ dangerous_patterns = ["/etc/", "/var/", ".env", "secret", "credential"]
265
+ for pattern in dangerous_patterns:
266
+ if pattern.lower() in target.lower():
267
+ factors.append(f"Target contains sensitive pattern: {pattern}")
268
+
269
+ if context:
270
+ source_role = context.get("source_role_id", "")
271
+ if source_role == "solo-coder" and category == OperationCategory.FORBIDDEN:
272
+ factors.append("Coder attempting forbidden operation")
273
+
274
+ return factors
275
+
276
+
277
+ def create_default_classifier() -> OperationClassifier:
278
+ """Create classifier with default classifications."""
279
+ return OperationClassifier()
280
+
281
+
282
+ def create_strict_classifier(
283
+ custom_classifications: Optional[Dict[str, OperationCategory]] = None,
284
+ ) -> OperationClassifier:
285
+ """Create classifier in strict mode (unknown ops = FORBIDDEN)."""
286
+ return OperationClassifier(
287
+ custom_classifications=custom_classifications,
288
+ strict_mode=True,
289
+ )