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,865 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ PermissionGuard - 4-Level Permission Guard System
5
+
6
+ Based on v3-phase2-permission-design.md:
7
+ Level DEFAULT: Dangerous ops require user confirmation
8
+ Level PLAN: Read-only mode, all writes denied
9
+ Level AUTO: AI classifier auto-judges + whitelist
10
+ Level BYPASS: Skip all checks (highest trust only)
11
+
12
+ Core components:
13
+ - ActionType: 9 operation types (FILE_READ/WRITE/DELETE/SHELL/NETWORK/GIT/ENV/PROCESS)
14
+ - PermissionLevel: 4 security levels (DEFAULT/PLAN/AUTO/BYPASS)
15
+ - DecisionOutcome: 4 verdicts (ALLOWED/DENIED/PROMPT/ESCALATED)
16
+ - Rule Engine: glob + prefix + regex pattern matching
17
+ - AI Classifier: 5-dimension risk scoring [0.0, 1.0]
18
+ - Audit Log: complete decision trail with filtering
19
+ """
20
+
21
+ import re
22
+ import fnmatch
23
+ import uuid
24
+ import threading
25
+ import time
26
+ from dataclasses import dataclass, field
27
+ from datetime import datetime
28
+ from enum import Enum
29
+ from typing import Any, Callable, Dict, List, Optional, Set, Tuple
30
+
31
+
32
+ class PermissionLevel(Enum):
33
+ DEFAULT = "default"
34
+ PLAN = "plan"
35
+ AUTO = "auto"
36
+ BYPASS = "bypass"
37
+
38
+
39
+ class ActionType(Enum):
40
+ FILE_READ = "file_read"
41
+ FILE_CREATE = "file_create"
42
+ FILE_MODIFY = "file_modify"
43
+ FILE_DELETE = "file_delete"
44
+ SHELL_EXECUTE = "shell_execute"
45
+ NETWORK_REQUEST = "network_request"
46
+ GIT_OPERATION = "git_operation"
47
+ ENVIRONMENT = "environment"
48
+ PROCESS_SPAWN = "process_spawn"
49
+
50
+
51
+ class DecisionOutcome(Enum):
52
+ ALLOWED = "allowed"
53
+ DENIED = "denied"
54
+ PROMPT = "prompt"
55
+ ESCALATED = "escalated"
56
+
57
+
58
+ @dataclass
59
+ class ProposedAction:
60
+ action_type: ActionType = ActionType.FILE_READ
61
+ target: str = ""
62
+ description: str = ""
63
+ source_worker_id: Optional[str] = None
64
+ source_role_id: Optional[str] = None
65
+ risk_score: float = 0.0
66
+ metadata: Dict[str, Any] = field(default_factory=dict)
67
+ timestamp: datetime = field(default_factory=datetime.now)
68
+
69
+ def to_dict(self) -> Dict:
70
+ return {
71
+ "action_type": self.action_type.value,
72
+ "target": self.target,
73
+ "description": self.description,
74
+ "source_worker_id": self.source_worker_id,
75
+ "source_role_id": self.source_role_id,
76
+ "risk_score": self.risk_score,
77
+ "metadata": self.metadata,
78
+ "timestamp": self.timestamp.isoformat(),
79
+ }
80
+
81
+ @classmethod
82
+ def from_dict(cls, d: Dict) -> "ProposedAction":
83
+ ts = d.get("timestamp")
84
+ return cls(
85
+ action_type=ActionType(d.get("action_type", "file_read")),
86
+ target=d.get("target", ""),
87
+ description=d.get("description", ""),
88
+ source_worker_id=d.get("source_worker_id"),
89
+ source_role_id=d.get("source_role_id"),
90
+ risk_score=d.get("risk_score", 0.0),
91
+ metadata=d.get("metadata", {}),
92
+ timestamp=datetime.fromisoformat(ts) if ts else datetime.now(),
93
+ )
94
+
95
+
96
+ @dataclass
97
+ class PermissionRule:
98
+ rule_id: str
99
+ action_type: ActionType
100
+ pattern: str
101
+ required_level: PermissionLevel
102
+ description: str = ""
103
+ risk_boost: float = 0.0
104
+ tags: List[str] = field(default_factory=list)
105
+ enabled: bool = True
106
+
107
+ def to_dict(self) -> Dict:
108
+ return {
109
+ "rule_id": self.rule_id,
110
+ "action_type": self.action_type.value,
111
+ "pattern": self.pattern,
112
+ "required_level": self.required_level.value,
113
+ "description": self.description,
114
+ "risk_boost": self.risk_boost,
115
+ "tags": self.tags,
116
+ "enabled": self.enabled,
117
+ }
118
+
119
+ @classmethod
120
+ def from_dict(cls, d: Dict) -> "PermissionRule":
121
+ return cls(
122
+ rule_id=d["rule_id"],
123
+ action_type=ActionType(d["action_type"]),
124
+ pattern=d["pattern"],
125
+ required_level=PermissionLevel(d["required_level"]),
126
+ description=d.get("description", ""),
127
+ risk_boost=d.get("risk_boost", 0.0),
128
+ tags=d.get("tags", []),
129
+ enabled=d.get("enabled", True),
130
+ )
131
+
132
+
133
+ @dataclass
134
+ class PermissionDecision:
135
+ action: ProposedAction
136
+ outcome: DecisionOutcome
137
+ matched_rule: Optional[PermissionRule] = None
138
+ reason: str = ""
139
+ requires_confirmation: bool = False
140
+ confidence: float = 1.0
141
+ decided_at: datetime = field(default_factory=datetime.now)
142
+ decision_id: str = field(default_factory=lambda: f"pd-{uuid.uuid4().hex[:12]}")
143
+
144
+ def to_dict(self) -> Dict:
145
+ return {
146
+ "decision_id": self.decision_id,
147
+ "outcome": self.outcome.value,
148
+ "matched_rule": self.matched_rule.rule_id if self.matched_rule else None,
149
+ "reason": self.reason,
150
+ "requires_confirmation": self.requires_confirmation,
151
+ "confidence": self.confidence,
152
+ "decided_at": self.decided_at.isoformat(),
153
+ "action": self.action.to_dict(),
154
+ }
155
+
156
+
157
+ @dataclass
158
+ class AuditEntry:
159
+ entry_id: str = field(default_factory=lambda: f"ae-{uuid.uuid4().hex[:12]}")
160
+ action: Optional[ProposedAction] = None
161
+ decision: Optional[PermissionDecision] = None
162
+ duration_ms: int = 0
163
+ guard_level: PermissionLevel = PermissionLevel.DEFAULT
164
+ user_response: Optional[str] = None
165
+ session_id: str = field(default_factory=lambda: f"sess-{uuid.uuid4().hex[:8]}")
166
+ timestamp: datetime = field(default_factory=datetime.now)
167
+
168
+ def to_dict(self) -> Dict:
169
+ return {
170
+ "entry_id": self.entry_id,
171
+ "action": self.action.to_dict() if self.action else None,
172
+ "decision": self.decision.to_dict() if self.decision else None,
173
+ "duration_ms": self.duration_ms,
174
+ "guard_level": self.guard_level.value,
175
+ "user_response": self.user_response,
176
+ "session_id": self.session_id,
177
+ "timestamp": self.timestamp.isoformat(),
178
+ }
179
+
180
+
181
+ # ============================================================
182
+ # Default Rules (30 rules covering common scenarios)
183
+ # ============================================================
184
+
185
+ def _build_default_rules() -> List[PermissionRule]:
186
+ return [
187
+ # File Read (low risk)
188
+ PermissionRule("R001", ActionType.FILE_READ, "**/*",
189
+ PermissionLevel.PLAN, "Read any file", risk_boost=0.0),
190
+
191
+ # File Create (medium risk)
192
+ PermissionRule("R002", ActionType.FILE_CREATE, "*.py",
193
+ PermissionLevel.AUTO, "Create Python file", risk_boost=0.1),
194
+ PermissionRule("R003", ActionType.FILE_CREATE, "*.md",
195
+ PermissionLevel.AUTO, "Create markdown doc", risk_boost=0.05),
196
+ PermissionRule("R004", ActionType.FILE_CREATE, "*.json",
197
+ PermissionLevel.DEFAULT, "Create JSON data file", risk_boost=0.15),
198
+ PermissionRule("R005", ActionType.FILE_CREATE, "*",
199
+ PermissionLevel.DEFAULT, "Create other file type", risk_boost=0.2),
200
+
201
+ # File Modify (medium-high risk)
202
+ PermissionRule("R006", ActionType.FILE_MODIFY, "*.py",
203
+ PermissionLevel.AUTO, "Modify Python source", risk_boost=0.2),
204
+ PermissionRule("R007", ActionType.FILE_MODIFY, "*.md",
205
+ PermissionLevel.AUTO, "Modify document", risk_boost=0.1),
206
+ PermissionRule("R008", ActionType.FILE_MODIFY, "*.json",
207
+ PermissionLevel.DEFAULT, "Modify config file", risk_boost=0.25),
208
+ PermissionRule("R009", ActionType.FILE_MODIFY, ".env*",
209
+ PermissionLevel.BYPASS, "Modify env file", risk_boost=0.8),
210
+ PermissionRule("R010", ActionType.FILE_MODIFY, "*credentials*",
211
+ PermissionLevel.BYPASS, "Modify credentials", risk_boost=0.95),
212
+ PermissionRule("R011", ActionType.FILE_MODIFY, "*",
213
+ PermissionLevel.DEFAULT, "Modify other files", risk_boost=0.25),
214
+
215
+ # File Delete (high risk)
216
+ PermissionRule("R012", ActionType.FILE_DELETE, "__pycache__/**",
217
+ PermissionLevel.AUTO, "Delete Python cache", risk_boost=0.1),
218
+ PermissionRule("R013", ActionType.FILE_DELETE, "*.pyc",
219
+ PermissionLevel.AUTO, "Delete compiled cache", risk_boost=0.1),
220
+ PermissionRule("R014", ActionType.FILE_DELETE, ".git/**",
221
+ PermissionLevel.BYPASS, "Delete Git dir content", risk_boost=0.99),
222
+ PermissionRule("R015", ActionType.FILE_DELETE, "*",
223
+ PermissionLevel.BYPASS, "Delete any file", risk_boost=0.9),
224
+
225
+ # Shell Execute (very high risk)
226
+ PermissionRule("R016", ActionType.SHELL_EXECUTE, "cat *",
227
+ PermissionLevel.AUTO, "View file content", risk_boost=0.05),
228
+ PermissionRule("R017", ActionType.SHELL_EXECUTE, "ls *",
229
+ PermissionLevel.AUTO, "List directory", risk_boost=0.05),
230
+ PermissionRule("R018", ActionType.SHELL_EXECUTE, "git *",
231
+ PermissionLevel.AUTO, "Git read-only commands", risk_boost=0.1),
232
+ PermissionRule("R019", ActionType.SHELL_EXECUTE, "pip install *",
233
+ PermissionLevel.DEFAULT, "Install Python package", risk_boost=0.5),
234
+ PermissionRule("R020", ActionType.SHELL_EXECUTE, "rm *",
235
+ PermissionLevel.BYPASS, "Remove command", risk_boost=0.95),
236
+ PermissionRule("R021", ActionType.SHELL_EXECUTE, "sudo *",
237
+ PermissionLevel.BYPASS, "Privilege escalation", risk_boost=1.0),
238
+ PermissionRule("R022", ActionType.SHELL_EXECUTE, "*",
239
+ PermissionLevel.DEFAULT, "Other shell commands", risk_boost=0.6),
240
+
241
+ # Network Request (medium-high risk)
242
+ PermissionRule("R023", ActionType.NETWORK_REQUEST, "*pypi.org*",
243
+ PermissionLevel.AUTO, "PyPI download", risk_boost=0.15),
244
+ PermissionRule("R024", ActionType.NETWORK_REQUEST, "*github.com*",
245
+ PermissionLevel.DEFAULT, "GitHub API access", risk_boost=0.3),
246
+ PermissionRule("R025", ActionType.NETWORK_REQUEST, "*",
247
+ PermissionLevel.DEFAULT, "Other network requests", risk_boost=0.5),
248
+
249
+ # Git Operations
250
+ PermissionRule("R026", ActionType.GIT_OPERATION, "*",
251
+ PermissionLevel.AUTO, "Git read operations", risk_boost=0.05),
252
+ PermissionRule("R027", ActionType.GIT_OPERATION, "*commit*|*add*",
253
+ PermissionLevel.DEFAULT, "Git commit ops", risk_boost=0.3),
254
+ PermissionRule("R028", ActionType.GIT_OPERATION, "*push*",
255
+ PermissionLevel.DEFAULT, "Git push operation", risk_boost=0.4),
256
+ PermissionRule("R029", ActionType.GIT_OPERATION, "*reset*|*rebase*|*force*",
257
+ PermissionLevel.BYPASS, "Dangerous git ops", risk_boost=0.9),
258
+
259
+ # Environment
260
+ PermissionRule("R030", ActionType.ENVIRONMENT, "*",
261
+ PermissionLevel.BYPASS, "Modify environment vars", risk_boost=0.7),
262
+ ]
263
+
264
+
265
+ # Sensitive keyword patterns for path traversal / injection detection
266
+ _SENSITIVE_PATH_PATTERNS = [
267
+ r"\.\./",
268
+ r"\.\.\\",
269
+ r"\x00",
270
+ r"/etc/passwd",
271
+ r"/etc/shadow",
272
+ ]
273
+
274
+ _SENSITIVE_PATH_PATTERNS_COMPILED = [
275
+ re.compile(p, re.IGNORECASE) for p in _SENSITIVE_PATH_PATTERNS
276
+ ]
277
+
278
+ _SENSITIVE_KEYWORDS = [
279
+ "credential", "secret", "password", "private_key", "api_key",
280
+ "token", ".env", ".pem", ".key", "id_rsa", ".ssh",
281
+ ]
282
+
283
+
284
+ class PermissionGuard:
285
+ """
286
+ 4级权限守卫系统 - 多 Agent 协作的安全操作检查核心
287
+
288
+ 安全级别(由低到高):
289
+ PLAN (0): 只读模式,所有写操作被拒绝
290
+ DEFAULT (1): 危险操作需用户确认
291
+ AUTO (2): AI 分类器自动判断 + 白名单放行
292
+ BYPASS (3): 跳过所有检查(最高信任,仅限受控环境)
293
+
294
+ 核心能力:
295
+ - check(): 对 ProposedAction 进行权限决策(ALLOWED/DENIED/PROMPT/ESCALATED)
296
+ - auto_classify(): 5维风险评分模型(目标敏感性/破坏性/范围/来源可信度/上下文合理性)
297
+ - 规则引擎: 30条默认规则覆盖 9种操作类型,支持 glob/前缀/regex 模式匹配
298
+ - 审计日志: 完整的决策链路记录,支持多维度过滤查询
299
+
300
+ 决策流程:
301
+ BYPASS → 直接放行
302
+ PLAN → 只读放行/写入拒绝
303
+ DEFAULT/AUTO → 白名单 → 规则匹配 → 风险评估 → 允许/提示/拒绝
304
+
305
+ 使用示例:
306
+ guard = PermissionGuard(current_level=PermissionLevel.DEFAULT)
307
+ action = ProposedAction(
308
+ action_type=ActionType.FILE_CREATE,
309
+ target="/path/to/file.py",
310
+ description="创建新模块",
311
+ source_worker_id="arch-abc",
312
+ source_role_id="architect",
313
+ )
314
+ decision = guard.check(action)
315
+ if decision.outcome == DecisionOutcome.PROMPT:
316
+ print(f"需用户确认: {decision.reason}")
317
+ """
318
+
319
+ LEVEL_ORDER = {
320
+ PermissionLevel.PLAN: 0,
321
+ PermissionLevel.DEFAULT: 1,
322
+ PermissionLevel.AUTO: 2,
323
+ PermissionLevel.BYPASS: 3,
324
+ }
325
+
326
+ def __init__(self,
327
+ current_level: PermissionLevel = PermissionLevel.DEFAULT,
328
+ rules: Optional[List[PermissionRule]] = None,
329
+ audit_log: bool = True,
330
+ session_id: Optional[str] = None):
331
+ """
332
+ 初始化权限守卫
333
+
334
+ Args:
335
+ current_level: 当前安全级别(默认 DEFAULT)
336
+ rules: 自定义规则列表(为空则使用 30 条内置默认规则)
337
+ audit_log: 是否启用审计日志记录
338
+ session_id: 会话标识(用于审计追踪,自动生成如未提供)
339
+ """
340
+ self.current_level = current_level
341
+ self.rules: List[PermissionRule] = rules or _build_default_rules()
342
+ self._rule_index: Dict[str, int] = {
343
+ r.rule_id: i for i, r in enumerate(self.rules)
344
+ }
345
+ self._rules_by_type: Dict[ActionType, List[PermissionRule]] = {}
346
+ for r in self.rules:
347
+ self._rules_by_type.setdefault(r.action_type, []).append(r)
348
+ self.audit_log_enabled = audit_log
349
+ self._audit_log: List[AuditEntry] = []
350
+ self._whitelist: Set[str] = set()
351
+ self._lock = threading.RLock()
352
+ self.session_id = session_id or f"sess-{uuid.uuid4().hex[:8]}"
353
+
354
+ def check(self, action: ProposedAction) -> PermissionDecision:
355
+ """
356
+ 核心权限检查方法 - 对操作提案进行安全决策
357
+
358
+ 决策流程(按优先级):
359
+ 1. BYPASS 级别 → 直接 ALLOWED
360
+ 2. PLAN 级别 → 只读允许 / 写入 DENIED
361
+ 3. 白名单匹配 → 直接 ALLOWED
362
+ 4. 规则匹配 → 结合风险评分决策:
363
+ - 风险 < 0.3 且级别充足 → ALLOWED
364
+ - 风险 0.3~0.7 或 AUTO 模式 → 调用 auto_classify() 综合判断
365
+ - 风险 > 0.7 → PROMPT (需用户确认)
366
+ 5. 无匹配规则 → 低风险允许 / 高风险 PROMPT
367
+
368
+ 所有决策都会记录到审计日志。
369
+
370
+ Args:
371
+ action: 操作提案,包含类型、目标路径、描述、来源信息等
372
+
373
+ Returns:
374
+ PermissionDecision: 权限决策结果,包含:
375
+ - outcome: 最终裁决 (ALLOWED/DENIED/PROMPT/ESCALATED)
376
+ - reason: 决策原因说明
377
+ - matched_rule: 匹配到的规则(如有)
378
+ - requires_confirmation: 是否需要用户确认
379
+ - confidence: 决策置信度 [0.1, 1.0]
380
+ - risk_score: 计算后的风险评分 [0.0, 1.0]
381
+ """
382
+ with self._lock:
383
+ start = time.perf_counter()
384
+
385
+ if self.current_level == PermissionLevel.BYPASS:
386
+ decision = self._make_decision(action, DecisionOutcome.ALLOWED,
387
+ "BYPASS模式: 跳过所有检查")
388
+ self._record_audit(action, decision, start)
389
+ return decision
390
+
391
+ if self.current_level == PermissionLevel.PLAN:
392
+ if action.action_type == ActionType.FILE_READ:
393
+ decision = self._make_decision(action, DecisionOutcome.ALLOWED,
394
+ "PLAN模式允许只读操作")
395
+ else:
396
+ decision = self._make_decision(action, DecisionOutcome.DENIED,
397
+ f"PLAN模式禁止{action.action_type.value}操作")
398
+ self._record_audit(action, decision, start)
399
+ return decision
400
+
401
+ target_str = action.target or ""
402
+ if target_str in self._whitelist or any(
403
+ fnmatch.fnmatch(target_str, p) for p in self._whitelist
404
+ ):
405
+ decision = self._make_decision(action, DecisionOutcome.ALLOWED,
406
+ "白名单匹配,直接放行")
407
+ self._record_audit(action, decision, start)
408
+ return decision
409
+
410
+ matched_rule = self._match_rule(action)
411
+ base_risk = self._assess_base_risk(action)
412
+
413
+ if matched_rule:
414
+ rule_level_val = self.LEVEL_ORDER.get(matched_rule.required_level, 1)
415
+ current_level_val = self.LEVEL_ORDER.get(self.current_level, 1)
416
+
417
+ effective_risk = min(1.0, base_risk + matched_rule.risk_boost)
418
+
419
+ if current_level_val >= rule_level_val:
420
+ if effective_risk < 0.3:
421
+ outcome = DecisionOutcome.ALLOWED
422
+ reason = f"规则{matched_rule.rule_id}匹配, 风险低({effective_risk:.2f})"
423
+ elif effective_risk < 0.7 or self.current_level == PermissionLevel.AUTO:
424
+ auto_score = self.auto_classify(action)
425
+ combined = (effective_risk + auto_score) / 2
426
+ if combined < 0.55:
427
+ outcome = DecisionOutcome.ALLOWED
428
+ reason = f"规则{matched_rule.rule_id}匹配, 综合风险可接受({combined:.2f})"
429
+ else:
430
+ outcome = DecisionOutcome.PROMPT
431
+ reason = f"规则{matched_rule.rule_id}匹配, 需确认(风险{combined:.2f})"
432
+ else:
433
+ outcome = DecisionOutcome.PROMPT
434
+ reason = f"规则{matched_rule.rule_id}高风险, 需用户确认(风险{effective_risk:.2f})"
435
+ else:
436
+ if matched_rule.required_level == PermissionLevel.BYPASS:
437
+ outcome = DecisionOutcome.PROMPT
438
+ reason = f"BYPASS级操作需人工确认(规则{matched_rule.rule_id}, 风险{effective_risk:.2f})"
439
+ elif effective_risk > 0.85 and self.current_level == PermissionLevel.PLAN:
440
+ outcome = DecisionOutcome.DENIED
441
+ reason = f"当前级别不足且高风险(需{matched_rule.required_level.value}, 当前{self.current_level.value})"
442
+ else:
443
+ outcome = DecisionOutcome.PROMPT
444
+ reason = f"当前级别不足(需{matched_rule.required_level.value}, 当前{self.current_level.value}), 请确认"
445
+
446
+ action.risk_score = effective_risk
447
+ requires_conf = outcome == DecisionOutcome.PROMPT
448
+ decision = PermissionDecision(
449
+ action=action,
450
+ outcome=outcome,
451
+ matched_rule=matched_rule,
452
+ reason=reason,
453
+ requires_confirmation=requires_conf,
454
+ confidence=max(0.1, 1.0 - effective_risk),
455
+ )
456
+ else:
457
+ fallback_risk = base_risk
458
+ action.risk_score = fallback_risk
459
+ if fallback_risk < 0.3:
460
+ outcome = DecisionOutcome.ALLOWED
461
+ reason = "无匹配规则, 低风险默认允许"
462
+ elif self.current_level == PermissionLevel.AUTO:
463
+ auto_score = self.auto_classify(action)
464
+ if auto_score < 0.4:
465
+ outcome = DecisionOutcome.ALLOWED
466
+ reason = f"AUTO分类安全(score={auto_score:.2f})"
467
+ else:
468
+ outcome = DecisionOutcome.PROMPT
469
+ reason = f"AUTO分类需确认(score={auto_score:.2f})"
470
+ else:
471
+ outcome = DecisionOutcome.PROMPT
472
+ reason = "无匹配规则, 默认需确认"
473
+
474
+ requires_conf = outcome == DecisionOutcome.PROMPT
475
+ decision = PermissionDecision(
476
+ action=action,
477
+ outcome=outcome,
478
+ matched_rule=None,
479
+ reason=reason,
480
+ requires_confirmation=requires_conf,
481
+ confidence=0.7,
482
+ )
483
+
484
+ self._record_audit(action, decision, start)
485
+ return decision
486
+
487
+ def auto_classify(self, action: ProposedAction) -> float:
488
+ """
489
+ AI 风险分类器 - 5维加权评分模型
490
+
491
+ 对操作提案进行多维度风险评估,返回 [0.0, 1.0] 的风险分数。
492
+ 各维度及权重:
493
+ - 目标敏感性 (30%): 路径是否包含敏感关键词/敏感路径模式
494
+ - 破坏性 (25%): 是否包含删除/覆盖/强制等破坏性关键词
495
+ - 作用范围 (20%): 通配符/超长目标路径
496
+ - 来源可信度 (15%): Worker 角色是否在已知信任列表
497
+ - 上下文合理性 (10%): 操作是否与任务相关、描述是否充分
498
+
499
+ Args:
500
+ action: 待评估的操作提案
501
+
502
+ Returns:
503
+ float: 风险评分 [0.0, 1.0],越高越危险
504
+ - < 0.3: 低风险,可自动放行
505
+ - 0.3~0.7: 中等风险,需综合判断
506
+ - > 0.7: 高风险,建议用户确认
507
+ """
508
+ score = 0.0
509
+ target_lower = (action.target or "").lower()
510
+
511
+ dim_sensitivity = self._dim_target_sensitivity(target_lower)
512
+ dim_destructive = self._dim_destructiveness(action)
513
+ dim_scope = self._dim_scope(action.target or "")
514
+ dim_source = self._dim_source_trust(action.source_role_id)
515
+ dim_context = self._dim_context_reasonable(action)
516
+
517
+ weights = [0.30, 0.25, 0.20, 0.15, 0.10]
518
+ dims = [dim_sensitivity, dim_destructive, dim_scope, dim_source, dim_context]
519
+ score = sum(w * d for w, d in zip(weights, dims))
520
+ return max(0.0, min(1.0, score))
521
+
522
+ def _match_rule(self, action: ProposedAction) -> Optional[PermissionRule]:
523
+ best_match = None
524
+ best_strictness = -1
525
+ candidates = self._rules_by_type.get(action.action_type, [])
526
+ for rule in candidates:
527
+ if not rule.enabled:
528
+ continue
529
+ if self._pattern_match(rule.pattern, action.target or ""):
530
+ strictness = self.LEVEL_ORDER.get(rule.required_level, 0)
531
+ if strictness > best_strictness:
532
+ best_match = rule
533
+ best_strictness = strictness
534
+ return best_match
535
+
536
+ def _pattern_match(self, pattern: str, target: str) -> bool:
537
+ if not target:
538
+ return False
539
+ try:
540
+ if fnmatch.fnmatch(target, pattern):
541
+ return True
542
+ if target.startswith(pattern.rstrip("*")):
543
+ return True
544
+ if re.search(pattern, target, re.IGNORECASE):
545
+ return True
546
+ except (re.error, TypeError):
547
+ pass
548
+ if pattern.endswith("*") and target.startswith(pattern[:-1]):
549
+ return True
550
+ return False
551
+
552
+ def _assess_base_risk(self, action: ProposedAction) -> float:
553
+ risk = 0.0
554
+ target = action.target or ""
555
+
556
+ for kw in _SENSITIVE_KEYWORDS:
557
+ if kw.lower() in target.lower():
558
+ risk += 0.15
559
+
560
+ for pat in _SENSITIVE_PATH_PATTERNS_COMPILED:
561
+ if pat.search(target):
562
+ risk += 0.25
563
+
564
+ high_risk_actions = {
565
+ ActionType.FILE_DELETE: 0.4,
566
+ ActionType.SHELL_EXECUTE: 0.3,
567
+ ActionType.PROCESS_SPAWN: 0.35,
568
+ ActionType.ENVIRONMENT: 0.3,
569
+ }
570
+ risk += high_risk_actions.get(action.action_type, 0.0)
571
+
572
+ if len(target) < 3 and action.action_type != ActionType.FILE_READ:
573
+ risk += 0.1
574
+
575
+ return min(1.0, risk)
576
+
577
+ def _dim_target_sensitivity(self, target_lower: str) -> float:
578
+ score = 0.0
579
+ for kw in _SENSITIVE_KEYWORDS:
580
+ if kw in target_lower:
581
+ score += 0.2
582
+ for pat in _SENSITIVE_PATH_PATTERNS:
583
+ if re.search(pat, target_lower):
584
+ score += 0.3
585
+ return min(1.0, score)
586
+
587
+ def _dim_destructiveness(self, action: ProposedAction) -> float:
588
+ destructive_keywords = ["rm ", "rm-", "delete", "drop", "truncate", "overwrite",
589
+ "force", "-f", "--force", "clear", "reset", "sudo"]
590
+ content = ((action.target or "") + " " + (action.description or "")).lower()
591
+ count = sum(1 for kw in destructive_keywords if kw in content)
592
+ base = min(1.0, count * 0.25)
593
+ if action.action_type == ActionType.SHELL_EXECUTE and count > 0:
594
+ base = min(1.0, base + 0.2)
595
+ return base
596
+
597
+ def _dim_scope(self, target: str) -> float:
598
+ if "*" in target or "?" in target:
599
+ return 0.6
600
+ if len(target) > 200:
601
+ return 0.4
602
+ return 0.1
603
+
604
+ def _dim_source_trust(self, role_id: Optional[str]) -> float:
605
+ known_roles = {"architect", "product-manager", "tester",
606
+ "solo-coder", "ui-designer", "devops"}
607
+ if role_id and role_id in known_roles:
608
+ return 0.1
609
+ return 0.5
610
+
611
+ def _dim_context_reasonable(self, action: ProposedAction) -> bool:
612
+ if action.metadata.get("task_related"):
613
+ return 0.1
614
+ if action.description and len(action.description) > 20:
615
+ return 0.2
616
+ return 0.4
617
+
618
+ def _make_decision(self, action: ProposedAction,
619
+ outcome: DecisionOutcome, reason: str) -> PermissionDecision:
620
+ return PermissionDecision(
621
+ action=action,
622
+ outcome=outcome,
623
+ reason=reason,
624
+ confidence=1.0 if outcome != DecisionOutcome.ESCALATED else 0.5,
625
+ )
626
+
627
+ def _record_audit(self, action: ProposedAction,
628
+ decision: PermissionDecision, start_time: float):
629
+ if not self.audit_log_enabled:
630
+ return
631
+ duration_ms = int((time.perf_counter() - start_time) * 1000)
632
+ entry = AuditEntry(
633
+ action=action,
634
+ decision=decision,
635
+ duration_ms=duration_ms,
636
+ guard_level=self.current_level,
637
+ session_id=self.session_id,
638
+ )
639
+ self._audit_log.append(entry)
640
+ if len(self._audit_log) > 10000:
641
+ self._audit_log = self._audit_log[-5000:]
642
+
643
+ def add_rule(self, rule: PermissionRule) -> None:
644
+ """
645
+ 添加或更新权限规则
646
+
647
+ 如规则 ID 已存在则更新(替换原规则),否则追加到规则列表。
648
+ 同时维护按操作类型分组的索引以加速匹配。
649
+
650
+ Args:
651
+ rule: 要添加的权限规则(含 rule_id, action_type, pattern 等)
652
+ """
653
+ with self._lock:
654
+ if rule.rule_id in self._rule_index:
655
+ idx = self._rule_index[rule.rule_id]
656
+ old_type = self.rules[idx].action_type
657
+ if old_type != rule.action_type:
658
+ type_list = self._rules_by_type.get(old_type, [])
659
+ if rule in type_list:
660
+ type_list.remove(rule)
661
+ self._rules_by_type.setdefault(rule.action_type, []).append(rule)
662
+ self.rules[idx] = rule
663
+ else:
664
+ self._rule_index[rule.rule_id] = len(self.rules)
665
+ self.rules.append(rule)
666
+ self._rules_by_type.setdefault(rule.action_type, []).append(rule)
667
+
668
+ def remove_rule(self, rule_id: str) -> bool:
669
+ """
670
+ 按ID移除权限规则
671
+
672
+ Args:
673
+ rule_id: 要移除的规则标识符
674
+
675
+ Returns:
676
+ bool: 是否成功移除(False 表示规则不存在)
677
+ """
678
+ with self._lock:
679
+ if rule_id not in self._rule_index:
680
+ return False
681
+ idx = self._rule_index.pop(rule_id)
682
+ removed = self.rules.pop(idx)
683
+ type_list = self._rules_by_type.get(removed.action_type, [])
684
+ if removed in type_list:
685
+ type_list.remove(removed)
686
+ self._rule_index = {r.rule_id: i for i, r in enumerate(self.rules)}
687
+ return True
688
+
689
+ def set_level(self, level: PermissionLevel) -> None:
690
+ """
691
+ 动态切换安全级别
692
+
693
+ 可在运行时提升/降低安全级别,无需重新创建实例。
694
+ 典型场景:进入 Plan Mode 时切换为 PLAN 级别。
695
+
696
+ Args:
697
+ level: 目标安全级别
698
+ """
699
+ self.current_level = level
700
+
701
+ def get_audit_log(self,
702
+ since: Optional[datetime] = None,
703
+ until: Optional[datetime] = None,
704
+ action_type: Optional[ActionType] = None,
705
+ outcome: Optional[DecisionOutcome] = None,
706
+ worker_id: Optional[str] = None,
707
+ limit: Optional[int] = None) -> List[AuditEntry]:
708
+ """
709
+ 查询审计日志(支持多维度过滤)
710
+
711
+ Args:
712
+ since: 起始时间(含)
713
+ until: 截止时间(含)
714
+ action_type: 按操作类型过滤
715
+ outcome: 按决策结果过滤
716
+ worker_id: 按 Worker ID 过滤
717
+ limit: 最大返回条数
718
+
719
+ Returns:
720
+ List[AuditEntry]: 匹配的审计条目列表
721
+ """
722
+ results = self._audit_log
723
+ if since:
724
+ results = [e for e in results if e.timestamp >= since]
725
+ if until:
726
+ results = [e for e in results if e.timestamp <= until]
727
+ if action_type:
728
+ results = [e for e in results
729
+ if e.action and e.action.action_type == action_type]
730
+ if outcome:
731
+ results = [e for e in results
732
+ if e.decision and e.decision.outcome == outcome]
733
+ if worker_id:
734
+ results = [e for e in results
735
+ if e.action and e.action.source_worker_id == worker_id]
736
+ if limit is not None:
737
+ results = results[:limit]
738
+ return list(results)
739
+
740
+ def get_security_report(self) -> Dict[str, Any]:
741
+ """
742
+ 生成安全报告摘要
743
+
744
+ 聚合所有审计日志数据,计算统计指标:
745
+ - 各决策结果的计数和占比
746
+ - 平均风险评分
747
+ - 最常被拒绝的操作目标 (Top 5)
748
+ - 当前规则数和白名单数
749
+
750
+ Returns:
751
+ Dict[str, Any]: 安全报告字典
752
+ """
753
+ total = len(self._audit_log)
754
+ allowed = sum(1 for e in self._audit_log
755
+ if e.decision and e.decision.outcome == DecisionOutcome.ALLOWED)
756
+ denied = sum(1 for e in self._audit_log
757
+ if e.decision and e.decision.outcome == DecisionOutcome.DENIED)
758
+ prompted = sum(1 for e in self._audit_log
759
+ if e.decision and e.decision.outcome == DecisionOutcome.PROMPT)
760
+ escalated = sum(1 for e in self._audit_log
761
+ if e.decision and e.decision.outcome == DecisionOutcome.ESCALATED)
762
+
763
+ avg_risk = 0.0
764
+ if total > 0:
765
+ risks = [e.action.risk_score for e in self._audit_log if e.action]
766
+ avg_risk = sum(risks) / len(risks) if risks else 0.0
767
+
768
+ denied_targets: Dict[str, int] = {}
769
+ for e in self._audit_log:
770
+ if e.decision and e.decision.outcome == DecisionOutcome.DENIED and e.action:
771
+ t = e.action.target or "unknown"
772
+ denied_targets[t] = denied_targets.get(t, 0) + 1
773
+ top_denied = sorted(denied_targets.items(), key=lambda x: x[1], reverse=True)[:5]
774
+
775
+ return {
776
+ "total_checks": total,
777
+ "allowed": allowed,
778
+ "denied": denied,
779
+ "prompted": prompted,
780
+ "escalated": escalated,
781
+ "avg_risk_score": round(avg_risk, 3),
782
+ "top_denied_actions": top_denied,
783
+ "guard_level": self.current_level.value,
784
+ "rules_count": len(self.rules),
785
+ "whitelist_count": len(self._whitelist),
786
+ }
787
+
788
+ def add_whitelist(self, pattern: str) -> None:
789
+ """将模式加入白名单(匹配时直接放行,跳过规则检查)"""
790
+ self._whitelist.add(pattern)
791
+
792
+ def remove_whitelist(self, pattern: str) -> None:
793
+ """从白名单中移除指定模式"""
794
+ self._whitelist.discard(pattern)
795
+
796
+ def get_whitelist(self) -> Set[str]:
797
+ """
798
+ 获取当前白名单集合
799
+
800
+ Returns:
801
+ Set[str]: 白名单模式集合的副本
802
+ """
803
+ return set(self._whitelist)
804
+
805
+ def export_rules(self) -> List[Dict]:
806
+ """
807
+ 导出所有启用的规则为字典列表
808
+
809
+ Returns:
810
+ List[Dict]: 每条规则的 to_dict() 序列化结果
811
+ """
812
+ return [r.to_dict() for r in self.rules if r.enabled]
813
+
814
+ def import_rules(self, rules_data: List[Dict]) -> int:
815
+ """
816
+ 从字典列表批量导入规则
817
+
818
+ 跳过格式无效的条目,返回成功导入的数量。
819
+
820
+ Args:
821
+ rules_data: 规则字典列表(每项需含 rule_id, action_type, pattern, required_level)
822
+
823
+ Returns:
824
+ int: 成功导入的规则数量
825
+ """
826
+ count = 0
827
+ for rd in rules_data:
828
+ try:
829
+ rule = PermissionRule.from_dict(rd)
830
+ self.add_rule(rule)
831
+ count += 1
832
+ except (KeyError, ValueError):
833
+ continue
834
+ return count
835
+
836
+ def export_state(self) -> Dict:
837
+ """
838
+ 导出完整状态快照
839
+
840
+ 包含当前级别、所有规则、白名单、会话ID和审计计数。
841
+ 可用于持久化或跨实例迁移。
842
+
843
+ Returns:
844
+ Dict: 完整状态字典
845
+ """
846
+ with self._lock:
847
+ return {
848
+ "current_level": self.current_level.value,
849
+ "rules": [r.to_dict() for r in self.rules],
850
+ "whitelist": list(self._whitelist),
851
+ "session_id": self.session_id,
852
+ "audit_count": len(self._audit_log),
853
+ }
854
+
855
+ def clear_audit_log(self) -> int:
856
+ """
857
+ 清空审计日志
858
+
859
+ Returns:
860
+ int: 清空前日志中的条目数
861
+ """
862
+ with self._lock:
863
+ count = len(self._audit_log)
864
+ self._audit_log.clear()
865
+ return count