arkaos 2.17.1 → 2.17.4

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 (77) hide show
  1. package/VERSION +1 -1
  2. package/arka/SKILL.md +9 -67
  3. package/arka/skills/comfyui/SKILL.md +50 -12
  4. package/arka/skills/conclave/SKILL.md +43 -141
  5. package/arka/skills/conclave/references/advisors.md +36 -0
  6. package/arka/skills/human-writing/SKILL.md +15 -100
  7. package/arka/skills/human-writing/references/forbidden-patterns.md +32 -0
  8. package/config/hooks/post-tool-use.sh +72 -0
  9. package/config/hooks/session-start.sh +16 -0
  10. package/config/hooks/user-prompt-submit.ps1 +2 -2
  11. package/config/hooks/user-prompt-submit.sh +102 -26
  12. package/core/agents/__pycache__/behavior_enforcer.cpython-313.pyc +0 -0
  13. package/core/agents/__pycache__/dna_registry.cpython-313.pyc +0 -0
  14. package/core/agents/adapters/__pycache__/disc_adapter.cpython-313.pyc +0 -0
  15. package/core/agents/adapters/disc_adapter.py +149 -0
  16. package/core/agents/behavior_enforcer.py +255 -0
  17. package/core/agents/dna_registry.py +235 -0
  18. package/core/forge/__init__.py +36 -0
  19. package/core/forge/__pycache__/__init__.cpython-313.pyc +0 -0
  20. package/core/forge/__pycache__/orchestrator.cpython-313.pyc +0 -0
  21. package/core/forge/__pycache__/runtime_dispatcher.cpython-313.pyc +0 -0
  22. package/core/forge/orchestrator.py +770 -0
  23. package/core/forge/runtime_dispatcher.py +465 -0
  24. package/core/governance/__pycache__/quality_api.cpython-313.pyc +0 -0
  25. package/core/governance/__pycache__/quality_router.cpython-313.pyc +0 -0
  26. package/core/governance/__pycache__/review_workflow.cpython-313.pyc +0 -0
  27. package/core/governance/quality_api.py +280 -0
  28. package/core/governance/quality_router.py +304 -0
  29. package/core/governance/review_workflow.py +386 -0
  30. package/core/memory/__pycache__/compressor.cpython-313.pyc +0 -0
  31. package/core/memory/__pycache__/rehydrator.cpython-313.pyc +0 -0
  32. package/core/memory/__pycache__/session_store.cpython-313.pyc +0 -0
  33. package/core/memory/compressor.py +269 -0
  34. package/core/memory/rehydrator.py +204 -0
  35. package/core/memory/session_store.py +256 -0
  36. package/core/runtime/__pycache__/context_compactor.cpython-313.pyc +0 -0
  37. package/core/runtime/__pycache__/subagent.cpython-313.pyc +0 -0
  38. package/core/synapse/__init__.py +10 -3
  39. package/core/synapse/__pycache__/__init__.cpython-313.pyc +0 -0
  40. package/core/synapse/__pycache__/engine.cpython-313.pyc +0 -0
  41. package/core/synapse/__pycache__/kb_cache.cpython-313.pyc +0 -0
  42. package/core/synapse/__pycache__/layers.cpython-313.pyc +0 -0
  43. package/core/synapse/engine.py +27 -16
  44. package/core/synapse/kb_cache.py +382 -0
  45. package/core/synapse/layers.py +253 -50
  46. package/core/sync/__pycache__/self_healing.cpython-313.pyc +0 -0
  47. package/core/workflow/__pycache__/announcer.cpython-313.pyc +0 -0
  48. package/core/workflow/__pycache__/dashboard.cpython-313.pyc +0 -0
  49. package/core/workflow/__pycache__/enforcer.cpython-313.pyc +0 -0
  50. package/core/workflow/__pycache__/rules_registry.cpython-313.pyc +0 -0
  51. package/core/workflow/__pycache__/state.cpython-313.pyc +0 -0
  52. package/core/workflow/announcer.py +246 -0
  53. package/core/workflow/dashboard.py +194 -0
  54. package/core/workflow/enforcer.py +234 -0
  55. package/core/workflow/recovery.py +196 -0
  56. package/core/workflow/rules_registry.py +484 -0
  57. package/core/workflow/session_summary.py +204 -0
  58. package/core/workflow/state.py +12 -2
  59. package/departments/dev/SKILL.md +10 -42
  60. package/departments/dev/skills/agent-design/SKILL.md +6 -26
  61. package/departments/dev/skills/ci-cd-pipeline/SKILL.md +6 -29
  62. package/departments/dev/skills/db-schema/SKILL.md +6 -24
  63. package/departments/dev/skills/dependency-audit/SKILL.md +1 -3
  64. package/departments/dev/skills/incident/SKILL.md +6 -24
  65. package/departments/dev/skills/mcp-builder/SKILL.md +4 -17
  66. package/departments/dev/skills/observability/SKILL.md +6 -29
  67. package/departments/dev/skills/performance-profiler/SKILL.md +5 -26
  68. package/departments/dev/skills/rag-architect/SKILL.md +5 -23
  69. package/departments/dev/skills/release/SKILL.md +4 -17
  70. package/departments/dev/skills/spec/SKILL.md +47 -148
  71. package/departments/landing/skills/landing-gen/SKILL.md +3 -15
  72. package/departments/marketing/skills/cold-email/SKILL.md +5 -17
  73. package/departments/marketing/skills/programmatic-seo/SKILL.md +6 -21
  74. package/departments/strategy/skills/board-advisor/SKILL.md +7 -21
  75. package/package.json +1 -1
  76. package/pyproject.toml +1 -1
  77. package/scripts/synapse-bridge.py +27 -3
@@ -0,0 +1,280 @@
1
+ """Quality Gate API — submit deliverables, query status, record verdicts.
2
+
3
+ Provides a simple programmatic interface for the Quality Gate system:
4
+ - submit() — submit a deliverable for review
5
+ - query() — check status of a submission
6
+ - verdict() — record a reviewer's verdict
7
+ - list_pending() — get all pending reviews
8
+ """
9
+
10
+ import json
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ from core.governance.quality_router import (
15
+ QualityRouter,
16
+ ReviewAssignment,
17
+ ReviewerType,
18
+ DeliverableType,
19
+ ReviewPriority,
20
+ route_deliverable,
21
+ )
22
+ from core.governance.review_workflow import (
23
+ ReviewWorkflowEngine,
24
+ ReviewWorkflow,
25
+ ReviewPhase,
26
+ Verdict,
27
+ ReviewerOpinion,
28
+ )
29
+
30
+
31
+ _STATE_DIR = Path.home() / ".arkaos" / "quality-gate"
32
+ _QUEUE_FILE = _STATE_DIR / "queue.json"
33
+ _WORKFLOWS_FILE = _STATE_DIR / "workflows.json"
34
+
35
+
36
+ def _ensure_state_dir() -> None:
37
+ _STATE_DIR.mkdir(parents=True, exist_ok=True)
38
+
39
+
40
+ def _load_queue() -> list[dict]:
41
+ _ensure_state_dir()
42
+ if not _QUEUE_FILE.exists():
43
+ return []
44
+ try:
45
+ return json.loads(_QUEUE_FILE.read_text())
46
+ except (json.JSONDecodeError, OSError):
47
+ return []
48
+
49
+
50
+ def _save_queue(queue: list[dict]) -> None:
51
+ _ensure_state_dir()
52
+ _QUEUE_FILE.write_text(json.dumps(queue, indent=2))
53
+
54
+
55
+ def _load_workflows() -> list[dict]:
56
+ _ensure_state_dir()
57
+ if not _WORKFLOWS_FILE.exists():
58
+ return []
59
+ try:
60
+ return json.loads(_WORKFLOWS_FILE.read_text())
61
+ except (json.JSONDecodeError, OSError):
62
+ return []
63
+
64
+
65
+ def _save_workflows(workflows: list[dict]) -> None:
66
+ _ensure_state_dir()
67
+ _WORKFLOWS_FILE.write_text(json.dumps(workflows, indent=2))
68
+
69
+
70
+ def submit(
71
+ title: str,
72
+ description: str,
73
+ deliverable_type: str | None = None,
74
+ submitter: str = "unknown",
75
+ metadata: dict | None = None,
76
+ ) -> dict[str, Any]:
77
+ """Submit a deliverable for Quality Gate review.
78
+
79
+ Args:
80
+ title: Deliverable title
81
+ description: Brief description
82
+ deliverable_type: Type as string (auto-detected if None)
83
+ submitter: Agent ID of submitter
84
+ metadata: Additional context
85
+
86
+ Returns:
87
+ Dict with submission_id, reviewers, priority, and estimated_sla
88
+ """
89
+ detype = DeliverableType(deliverable_type) if deliverable_type else None
90
+
91
+ router = QualityRouter()
92
+ assignment = router.route(title, description, detype, submitter, metadata)
93
+
94
+ queue = _load_queue()
95
+ queue.append(
96
+ {
97
+ "id": assignment.id,
98
+ "title": assignment.title,
99
+ "deliverable_type": assignment.deliverable_type.value,
100
+ "submitter": assignment.submitter,
101
+ "reviewers": [r.value for r in assignment.reviewers],
102
+ "priority": assignment.priority.value,
103
+ "status": assignment.status,
104
+ "submitted_at": assignment.submitted_at,
105
+ "sla_due": assignment.sla_due,
106
+ }
107
+ )
108
+ _save_queue(queue)
109
+
110
+ return {
111
+ "submission_id": assignment.id,
112
+ "reviewers": [r.value for r in assignment.reviewers],
113
+ "priority": assignment.priority.value,
114
+ "estimated_sla": assignment.sla_due,
115
+ "deliverable_type": assignment.deliverable_type.value,
116
+ }
117
+
118
+
119
+ def query(submission_id: str) -> dict[str, Any] | None:
120
+ """Query the status of a submission.
121
+
122
+ Args:
123
+ submission_id: ID returned from submit()
124
+
125
+ Returns:
126
+ Status dict or None if not found
127
+ """
128
+ queue = _load_queue()
129
+ for item in queue:
130
+ if item["id"] == submission_id:
131
+ return item
132
+
133
+ workflows = _load_workflows()
134
+ for wf in workflows:
135
+ if wf["id"] == submission_id:
136
+ return wf
137
+
138
+ return None
139
+
140
+
141
+ def record_verdict(
142
+ submission_id: str,
143
+ reviewer: str,
144
+ verdict: str,
145
+ feedback: str = "",
146
+ flagged_rules: list[str] | None = None,
147
+ ) -> bool:
148
+ """Record a reviewer's verdict for a submission.
149
+
150
+ Args:
151
+ submission_id: ID of the submission
152
+ reviewer: Agent ID of reviewer
153
+ verdict: APPROVED, REJECTED, REQUEST_CHANGES, or ESCALATE
154
+ feedback: Feedback for the submitter
155
+ flagged_rules: List of Constitution rules violated
156
+
157
+ Returns:
158
+ True if verdict recorded successfully
159
+ """
160
+ reviewer_type = ReviewerType(reviewer) if reviewer in [r.value for r in ReviewerType] else None
161
+
162
+ queue = _load_queue()
163
+ for item in queue:
164
+ if item["id"] == submission_id:
165
+ item["status"] = "reviewed"
166
+ item["verdict"] = verdict
167
+ item["feedback"] = feedback
168
+ item["flagged_rules"] = flagged_rules or []
169
+ _save_queue(queue)
170
+ return True
171
+
172
+ return False
173
+
174
+
175
+ def list_pending(reviewer: str | None = None) -> list[dict]:
176
+ """List all pending submissions.
177
+
178
+ Args:
179
+ reviewer: If set, only show items pending for this reviewer
180
+
181
+ Returns:
182
+ List of pending submission dicts
183
+ """
184
+ queue = _load_queue()
185
+ pending = [item for item in queue if item["status"] == "pending"]
186
+
187
+ if reviewer:
188
+ pending = [item for item in pending if reviewer in item.get("reviewers", [])]
189
+
190
+ return pending
191
+
192
+
193
+ def list_approved(limit: int = 10) -> list[dict]:
194
+ """List recently approved submissions.
195
+
196
+ Args:
197
+ limit: Maximum number to return
198
+
199
+ Returns:
200
+ List of approved submission dicts
201
+ """
202
+ queue = _load_queue()
203
+ approved = [
204
+ item
205
+ for item in queue
206
+ if item.get("status") == "reviewed" and item.get("verdict") == "APPROVED"
207
+ ]
208
+ return approved[:limit]
209
+
210
+
211
+ def clear_resolved(older_than_days: int = 7) -> int:
212
+ """Remove resolved submissions older than specified days.
213
+
214
+ Args:
215
+ older_than_days: Remove items resolved more than this many days ago
216
+
217
+ Returns:
218
+ Number of items removed
219
+ """
220
+ from datetime import datetime, timedelta, timezone
221
+
222
+ cutoff = datetime.now(timezone.utc) - timedelta(days=older_than_days)
223
+ queue = _load_queue()
224
+ original_count = len(queue)
225
+
226
+ remaining = []
227
+ for item in queue:
228
+ if item["status"] == "pending":
229
+ remaining.append(item)
230
+ continue
231
+
232
+ verdict_at = item.get("verdict_at", item.get("submitted_at", ""))
233
+ try:
234
+ dt = datetime.fromisoformat(verdict_at.replace("Z", "+00:00"))
235
+ if dt > cutoff:
236
+ remaining.append(item)
237
+ except (ValueError, TypeError, AttributeError):
238
+ remaining.append(item)
239
+
240
+ _save_queue(remaining)
241
+ return original_count - len(remaining)
242
+
243
+
244
+ class QualityGateAPI:
245
+ """Class-based API for Quality Gate operations."""
246
+
247
+ def __init__(self):
248
+ _ensure_state_dir()
249
+
250
+ def submit(
251
+ self,
252
+ title: str,
253
+ description: str,
254
+ deliverable_type: str | None = None,
255
+ submitter: str = "unknown",
256
+ metadata: dict | None = None,
257
+ ) -> dict[str, Any]:
258
+ return submit(title, description, deliverable_type, submitter, metadata)
259
+
260
+ def query(self, submission_id: str) -> dict[str, Any] | None:
261
+ return query(submission_id)
262
+
263
+ def record_verdict(
264
+ self,
265
+ submission_id: str,
266
+ reviewer: str,
267
+ verdict: str,
268
+ feedback: str = "",
269
+ flagged_rules: list[str] | None = None,
270
+ ) -> bool:
271
+ return record_verdict(submission_id, reviewer, verdict, feedback, flagged_rules)
272
+
273
+ def list_pending(self, reviewer: str | None = None) -> list[dict]:
274
+ return list_pending(reviewer)
275
+
276
+ def list_approved(self, limit: int = 10) -> list[dict]:
277
+ return list_approved(limit)
278
+
279
+ def clear_resolved(self, older_than_days: int = 7) -> int:
280
+ return clear_resolved(older_than_days)
@@ -0,0 +1,304 @@
1
+ """Quality Gate Router — auto-routes deliverables to appropriate reviewers.
2
+
3
+ Routes deliverables to:
4
+ - Marta (CQO): Architecture, security, technical decisions
5
+ - Eduardo (Copy): All text, copy, documentation, content
6
+ - Francisca (Tech UX): UI/UX, frontend, user-facing code
7
+
8
+ Features:
9
+ - Automatic routing based on deliverable type
10
+ - Priority queuing
11
+ - SLA tracking (24h default)
12
+ - Escalation on BLOCK violations
13
+ """
14
+
15
+ import uuid
16
+ from dataclasses import dataclass, field
17
+ from datetime import datetime, timezone, timedelta
18
+ from enum import Enum
19
+ from pathlib import Path
20
+ from typing import Any
21
+
22
+
23
+ class ReviewerType(str, Enum):
24
+ CQO = "cqo-marta" # Marta - Chief Quality Officer
25
+ COPY = "copy-eduardo" # Eduardo - Copy Director
26
+ TECH_UX = "tech-francisca" # Francisca - Tech UX
27
+
28
+
29
+ class DeliverableType(str, Enum):
30
+ CODE = "code" # Backend/API code
31
+ FRONTEND = "frontend" # UI/UX, Vue/React
32
+ ARCHITECTURE = "architecture" # ADRs, system design
33
+ SECURITY = "security" # Security audits, auth
34
+ COPY = "copy" # Text, docs, content
35
+ DOCUMENTATION = "documentation" # Docs, READMEs
36
+ STRATEGY = "strategy" # Plans, roadmaps
37
+ DATA = "data" # Models, migrations
38
+
39
+
40
+ class ReviewPriority(str, Enum):
41
+ LOW = "low"
42
+ NORMAL = "normal"
43
+ HIGH = "high"
44
+ URGENT = "urgent"
45
+
46
+
47
+ @dataclass
48
+ class ReviewAssignment:
49
+ """A deliverable assigned for review."""
50
+
51
+ id: str
52
+ deliverable_type: DeliverableType
53
+ title: str
54
+ description: str
55
+ submitter: str
56
+ submitted_at: str
57
+ reviewers: list[ReviewerType]
58
+ priority: ReviewPriority = ReviewPriority.NORMAL
59
+ status: str = "pending"
60
+ verdict: str = ""
61
+ feedback: str = ""
62
+ sla_due: str = ""
63
+ metadata: dict = field(default_factory=dict)
64
+
65
+ def __post_init__(self):
66
+ if not self.sla_due:
67
+ self.sla_due = (datetime.now(timezone.utc) + timedelta(hours=24)).isoformat()
68
+
69
+
70
+ @dataclass
71
+ class RoutingRule:
72
+ """A rule for routing deliverables to reviewers."""
73
+
74
+ deliverable_types: list[DeliverableType]
75
+ keywords: list[str]
76
+ file_extensions: list[str]
77
+ reviewers: list[ReviewerType]
78
+ priority: ReviewPriority = ReviewPriority.NORMAL
79
+
80
+
81
+ DEFAULT_ROUTING_RULES: list[RoutingRule] = [
82
+ RoutingRule(
83
+ deliverable_types=[DeliverableType.ARCHITECTURE],
84
+ keywords=["architecture", "adr", "system design", "decision"],
85
+ file_extensions=[".md"],
86
+ reviewers=[ReviewerType.CQO],
87
+ priority=ReviewPriority.HIGH,
88
+ ),
89
+ RoutingRule(
90
+ deliverable_types=[DeliverableType.SECURITY],
91
+ keywords=["security", "auth", "permission", "oauth", "jwt"],
92
+ file_extensions=[],
93
+ reviewers=[ReviewerType.CQO, ReviewerType.TECH_UX],
94
+ priority=ReviewPriority.URGENT,
95
+ ),
96
+ RoutingRule(
97
+ deliverable_types=[DeliverableType.FRONTEND],
98
+ keywords=["vue", "react", "ui", "component", "tailwind", "css"],
99
+ file_extensions=[".vue", ".tsx", ".jsx"],
100
+ reviewers=[ReviewerType.TECH_UX],
101
+ priority=ReviewPriority.NORMAL,
102
+ ),
103
+ RoutingRule(
104
+ deliverable_types=[DeliverableType.CODE],
105
+ keywords=["api", "backend", "service", "model", "controller"],
106
+ file_extensions=[".py", ".php", ".java", ".go"],
107
+ reviewers=[ReviewerType.CQO],
108
+ priority=ReviewPriority.NORMAL,
109
+ ),
110
+ RoutingRule(
111
+ deliverable_types=[DeliverableType.COPY],
112
+ keywords=["copy", "headline", "cta", "landing", "sales"],
113
+ file_extensions=[],
114
+ reviewers=[ReviewerType.COPY],
115
+ priority=ReviewPriority.NORMAL,
116
+ ),
117
+ RoutingRule(
118
+ deliverable_types=[DeliverableType.DOCUMENTATION],
119
+ keywords=["doc", "readme", "guide", "docs"],
120
+ file_extensions=[".md", ".txt"],
121
+ reviewers=[ReviewerType.COPY, ReviewerType.TECH_UX],
122
+ priority=ReviewPriority.LOW,
123
+ ),
124
+ RoutingRule(
125
+ deliverable_types=[DeliverableType.STRATEGY],
126
+ keywords=["roadmap", "strategy", "plan", "okr", "kpi"],
127
+ file_extensions=[".md"],
128
+ reviewers=[ReviewerType.CQO, ReviewerType.COPY],
129
+ priority=ReviewPriority.HIGH,
130
+ ),
131
+ ]
132
+
133
+
134
+ class QualityRouter:
135
+ """Routes deliverables to appropriate Quality Gate reviewers."""
136
+
137
+ def __init__(self, rules: list[RoutingRule] | None = None):
138
+ self.rules = rules or DEFAULT_ROUTING_RULES
139
+ self._queue: list[ReviewAssignment] = []
140
+
141
+ def route(
142
+ self,
143
+ title: str,
144
+ description: str,
145
+ deliverable_type: DeliverableType | None = None,
146
+ submitter: str = "unknown",
147
+ metadata: dict | None = None,
148
+ ) -> ReviewAssignment:
149
+ """Route a deliverable to appropriate reviewers.
150
+
151
+ Args:
152
+ title: Deliverable title
153
+ description: Brief description
154
+ deliverable_type: Explicit type (auto-detected if None)
155
+ submitter: Agent ID of submitter
156
+ metadata: Additional context
157
+
158
+ Returns:
159
+ ReviewAssignment with assigned reviewers
160
+ """
161
+ if deliverable_type is None:
162
+ deliverable_type = self._detect_type(title, description, metadata)
163
+
164
+ reviewers = self._find_reviewers(deliverable_type, title, description, metadata)
165
+ priority = self._find_priority(deliverable_type, title, description)
166
+
167
+ assignment = ReviewAssignment(
168
+ id=str(uuid.uuid4()),
169
+ deliverable_type=deliverable_type,
170
+ title=title,
171
+ description=description,
172
+ submitter=submitter,
173
+ submitted_at=datetime.now(timezone.utc).isoformat(),
174
+ reviewers=reviewers,
175
+ priority=priority,
176
+ metadata=metadata or {},
177
+ )
178
+
179
+ self._queue.append(assignment)
180
+ return assignment
181
+
182
+ def _detect_type(
183
+ self,
184
+ title: str,
185
+ description: str,
186
+ metadata: dict | None,
187
+ ) -> DeliverableType:
188
+ """Auto-detect deliverable type from content."""
189
+ text = f"{title} {description}".lower()
190
+ metadata = metadata or {}
191
+
192
+ if any(kw in text for kw in ["security", "auth", "permission", "oauth"]):
193
+ return DeliverableType.SECURITY
194
+ if any(kw in text for kw in ["architecture", "adr", "system design"]):
195
+ return DeliverableType.ARCHITECTURE
196
+ if any(kw in text for kw in ["vue", "react", "ui", "component", "frontend"]):
197
+ return DeliverableType.FRONTEND
198
+ if any(kw in text for kw in ["api", "backend", "service", "model"]):
199
+ return DeliverableType.CODE
200
+ if any(kw in text for kw in ["copy", "headline", "cta", "sales", "landing"]):
201
+ return DeliverableType.COPY
202
+ if any(kw in text for kw in ["doc", "readme", "guide"]):
203
+ return DeliverableType.DOCUMENTATION
204
+ if any(kw in text for kw in ["roadmap", "strategy", "plan"]):
205
+ return DeliverableType.STRATEGY
206
+
207
+ if metadata.get("file_path"):
208
+ ext = Path(metadata["file_path"]).suffix.lower()
209
+ if ext in [".vue", ".tsx", ".jsx"]:
210
+ return DeliverableType.FRONTEND
211
+ if ext in [".py", ".php", ".java", ".go"]:
212
+ return DeliverableType.CODE
213
+
214
+ return DeliverableType.CODE
215
+
216
+ def _find_reviewers(
217
+ self,
218
+ deliverable_type: DeliverableType,
219
+ title: str,
220
+ description: str,
221
+ metadata: dict | None,
222
+ ) -> list[ReviewerType]:
223
+ """Find appropriate reviewers for this deliverable."""
224
+ for rule in self.rules:
225
+ if deliverable_type in rule.deliverable_types:
226
+ return rule.reviewers
227
+
228
+ return [ReviewerType.CQO]
229
+
230
+ def _find_priority(
231
+ self,
232
+ deliverable_type: DeliverableType,
233
+ title: str,
234
+ description: str,
235
+ ) -> ReviewPriority:
236
+ """Determine review priority."""
237
+ text = f"{title} {description}".lower()
238
+
239
+ if any(kw in text for kw in ["urgent", "asap", "critical", "blocker"]):
240
+ return ReviewPriority.URGENT
241
+
242
+ for rule in self.rules:
243
+ if deliverable_type in rule.deliverable_types:
244
+ return rule.priority
245
+
246
+ return ReviewPriority.NORMAL
247
+
248
+ def get_queue(self) -> list[ReviewAssignment]:
249
+ """Get current review queue."""
250
+ return self._queue.copy()
251
+
252
+ def get_pending_for_reviewer(self, reviewer: ReviewerType) -> list[ReviewAssignment]:
253
+ """Get pending items for a specific reviewer."""
254
+ return [a for a in self._queue if reviewer in a.reviewers and a.status == "pending"]
255
+
256
+ def get_assignment(self, assignment_id: str) -> ReviewAssignment | None:
257
+ """Get a specific assignment by ID."""
258
+ for a in self._queue:
259
+ if a.id == assignment_id:
260
+ return a
261
+ return None
262
+
263
+ def update_verdict(
264
+ self,
265
+ assignment_id: str,
266
+ verdict: str,
267
+ feedback: str,
268
+ reviewer: ReviewerType,
269
+ ) -> ReviewAssignment | None:
270
+ """Record a verdict for an assignment."""
271
+ assignment = self.get_assignment(assignment_id)
272
+ if not assignment:
273
+ return None
274
+
275
+ if reviewer not in assignment.reviewers:
276
+ return None
277
+
278
+ assignment.verdict = verdict
279
+ assignment.feedback = feedback
280
+ assignment.status = "reviewed"
281
+ return assignment
282
+
283
+ def is_sla_breached(self, assignment: ReviewAssignment) -> bool:
284
+ """Check if an assignment has breached SLA."""
285
+ try:
286
+ due = datetime.fromisoformat(assignment.sla_due)
287
+ return datetime.now(timezone.utc) > due
288
+ except (ValueError, TypeError):
289
+ return False
290
+
291
+ def get_breached_items(self) -> list[ReviewAssignment]:
292
+ """Get all assignments that have breached SLA."""
293
+ return [a for a in self._queue if self.is_sla_breached(a) and a.status == "pending"]
294
+
295
+
296
+ def route_deliverable(
297
+ title: str,
298
+ description: str,
299
+ deliverable_type: DeliverableType | None = None,
300
+ submitter: str = "unknown",
301
+ ) -> ReviewAssignment:
302
+ """Convenience function to route a deliverable."""
303
+ router = QualityRouter()
304
+ return router.route(title, description, deliverable_type, submitter)