monoco-toolkit 0.3.12__py3-none-any.whl → 0.4.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 (120) hide show
  1. monoco/core/automation/__init__.py +0 -11
  2. monoco/core/automation/handlers.py +108 -26
  3. monoco/core/config.py +28 -10
  4. monoco/core/daemon/__init__.py +5 -0
  5. monoco/core/daemon/pid.py +290 -0
  6. monoco/core/injection.py +86 -8
  7. monoco/core/integrations.py +0 -24
  8. monoco/core/router/__init__.py +1 -39
  9. monoco/core/router/action.py +3 -142
  10. monoco/core/scheduler/events.py +28 -2
  11. monoco/core/setup.py +9 -0
  12. monoco/core/sync.py +199 -4
  13. monoco/core/watcher/__init__.py +6 -0
  14. monoco/core/watcher/base.py +18 -1
  15. monoco/core/watcher/im.py +460 -0
  16. monoco/core/watcher/memo.py +40 -48
  17. monoco/daemon/app.py +3 -60
  18. monoco/daemon/commands.py +459 -25
  19. monoco/daemon/scheduler.py +1 -16
  20. monoco/daemon/services.py +15 -0
  21. monoco/features/agent/resources/en/AGENTS.md +14 -14
  22. monoco/features/agent/resources/en/skills/monoco_role_engineer/SKILL.md +101 -0
  23. monoco/features/agent/resources/en/skills/monoco_role_manager/SKILL.md +95 -0
  24. monoco/features/agent/resources/en/skills/monoco_role_planner/SKILL.md +177 -0
  25. monoco/features/agent/resources/en/skills/monoco_role_reviewer/SKILL.md +139 -0
  26. monoco/features/agent/resources/zh/skills/monoco_role_engineer/SKILL.md +101 -0
  27. monoco/features/agent/resources/zh/skills/monoco_role_manager/SKILL.md +95 -0
  28. monoco/features/agent/resources/zh/skills/monoco_role_planner/SKILL.md +177 -0
  29. monoco/features/agent/resources/zh/skills/monoco_role_reviewer/SKILL.md +139 -0
  30. monoco/features/hooks/__init__.py +61 -6
  31. monoco/features/hooks/commands.py +281 -271
  32. monoco/features/hooks/dispatchers/__init__.py +23 -0
  33. monoco/features/hooks/dispatchers/agent_dispatcher.py +486 -0
  34. monoco/features/hooks/dispatchers/git_dispatcher.py +478 -0
  35. monoco/features/hooks/manager.py +357 -0
  36. monoco/features/hooks/models.py +262 -0
  37. monoco/features/hooks/parser.py +322 -0
  38. monoco/features/hooks/universal_interceptor.py +503 -0
  39. monoco/features/im/__init__.py +67 -0
  40. monoco/features/im/core.py +782 -0
  41. monoco/features/im/models.py +311 -0
  42. monoco/features/issue/commands.py +65 -50
  43. monoco/features/issue/core.py +199 -99
  44. monoco/features/issue/domain_commands.py +0 -19
  45. monoco/features/issue/resources/en/AGENTS.md +17 -122
  46. monoco/features/issue/resources/hooks/agent/before-tool.sh +102 -0
  47. monoco/features/issue/resources/hooks/agent/session-start.sh +88 -0
  48. monoco/features/issue/resources/hooks/{post-checkout.sh → git/git-post-checkout.sh} +10 -9
  49. monoco/features/issue/resources/hooks/git/git-pre-commit.sh +31 -0
  50. monoco/features/issue/resources/hooks/{pre-push.sh → git/git-pre-push.sh} +7 -13
  51. monoco/features/issue/resources/zh/AGENTS.md +18 -123
  52. monoco/features/memo/cli.py +15 -64
  53. monoco/features/memo/core.py +6 -34
  54. monoco/features/memo/models.py +24 -15
  55. monoco/features/memo/resources/en/AGENTS.md +31 -0
  56. monoco/features/memo/resources/zh/AGENTS.md +28 -5
  57. monoco/main.py +5 -3
  58. {monoco_toolkit-0.3.12.dist-info → monoco_toolkit-0.4.0.dist-info}/METADATA +1 -1
  59. monoco_toolkit-0.4.0.dist-info/RECORD +170 -0
  60. monoco/core/automation/config.py +0 -338
  61. monoco/core/execution.py +0 -67
  62. monoco/core/executor/__init__.py +0 -38
  63. monoco/core/executor/agent_action.py +0 -254
  64. monoco/core/executor/git_action.py +0 -303
  65. monoco/core/executor/im_action.py +0 -309
  66. monoco/core/executor/pytest_action.py +0 -218
  67. monoco/core/router/router.py +0 -392
  68. monoco/features/agent/resources/atoms/atom-code-dev.yaml +0 -61
  69. monoco/features/agent/resources/atoms/atom-issue-lifecycle.yaml +0 -73
  70. monoco/features/agent/resources/atoms/atom-knowledge.yaml +0 -55
  71. monoco/features/agent/resources/atoms/atom-review.yaml +0 -60
  72. monoco/features/agent/resources/en/skills/monoco_atom_core/SKILL.md +0 -99
  73. monoco/features/agent/resources/en/skills/monoco_workflow_agent_engineer/SKILL.md +0 -94
  74. monoco/features/agent/resources/en/skills/monoco_workflow_agent_manager/SKILL.md +0 -93
  75. monoco/features/agent/resources/en/skills/monoco_workflow_agent_planner/SKILL.md +0 -85
  76. monoco/features/agent/resources/en/skills/monoco_workflow_agent_reviewer/SKILL.md +0 -114
  77. monoco/features/agent/resources/workflows/workflow-dev.yaml +0 -83
  78. monoco/features/agent/resources/workflows/workflow-issue-create.yaml +0 -72
  79. monoco/features/agent/resources/workflows/workflow-review.yaml +0 -94
  80. monoco/features/agent/resources/zh/roles/monoco_role_engineer.yaml +0 -49
  81. monoco/features/agent/resources/zh/roles/monoco_role_manager.yaml +0 -46
  82. monoco/features/agent/resources/zh/roles/monoco_role_planner.yaml +0 -46
  83. monoco/features/agent/resources/zh/roles/monoco_role_reviewer.yaml +0 -47
  84. monoco/features/agent/resources/zh/skills/monoco_atom_core/SKILL.md +0 -99
  85. monoco/features/agent/resources/zh/skills/monoco_workflow_agent_engineer/SKILL.md +0 -94
  86. monoco/features/agent/resources/zh/skills/monoco_workflow_agent_manager/SKILL.md +0 -88
  87. monoco/features/agent/resources/zh/skills/monoco_workflow_agent_planner/SKILL.md +0 -259
  88. monoco/features/agent/resources/zh/skills/monoco_workflow_agent_reviewer/SKILL.md +0 -137
  89. monoco/features/artifact/resources/zh/skills/monoco_atom_artifact/SKILL.md +0 -278
  90. monoco/features/glossary/resources/en/skills/monoco_atom_glossary/SKILL.md +0 -35
  91. monoco/features/glossary/resources/zh/skills/monoco_atom_glossary/SKILL.md +0 -35
  92. monoco/features/hooks/adapter.py +0 -67
  93. monoco/features/hooks/core.py +0 -441
  94. monoco/features/i18n/resources/en/skills/monoco_atom_i18n/SKILL.md +0 -96
  95. monoco/features/i18n/resources/en/skills/monoco_workflow_i18n_scan/SKILL.md +0 -105
  96. monoco/features/i18n/resources/zh/skills/monoco_atom_i18n/SKILL.md +0 -96
  97. monoco/features/i18n/resources/zh/skills/monoco_workflow_i18n_scan/SKILL.md +0 -105
  98. monoco/features/issue/resources/en/skills/monoco_atom_issue/SKILL.md +0 -165
  99. monoco/features/issue/resources/en/skills/monoco_workflow_issue_creation/SKILL.md +0 -167
  100. monoco/features/issue/resources/en/skills/monoco_workflow_issue_development/SKILL.md +0 -224
  101. monoco/features/issue/resources/en/skills/monoco_workflow_issue_management/SKILL.md +0 -159
  102. monoco/features/issue/resources/en/skills/monoco_workflow_issue_refinement/SKILL.md +0 -203
  103. monoco/features/issue/resources/hooks/pre-commit.sh +0 -41
  104. monoco/features/issue/resources/zh/skills/monoco_atom_issue_lifecycle/SKILL.md +0 -190
  105. monoco/features/issue/resources/zh/skills/monoco_workflow_issue_creation/SKILL.md +0 -167
  106. monoco/features/issue/resources/zh/skills/monoco_workflow_issue_development/SKILL.md +0 -224
  107. monoco/features/issue/resources/zh/skills/monoco_workflow_issue_management/SKILL.md +0 -159
  108. monoco/features/issue/resources/zh/skills/monoco_workflow_issue_refinement/SKILL.md +0 -203
  109. monoco/features/memo/resources/en/skills/monoco_atom_memo/SKILL.md +0 -77
  110. monoco/features/memo/resources/en/skills/monoco_workflow_note_processing/SKILL.md +0 -140
  111. monoco/features/memo/resources/zh/skills/monoco_atom_memo/SKILL.md +0 -77
  112. monoco/features/memo/resources/zh/skills/monoco_workflow_note_processing/SKILL.md +0 -140
  113. monoco/features/spike/resources/en/skills/monoco_atom_spike/SKILL.md +0 -76
  114. monoco/features/spike/resources/en/skills/monoco_workflow_research/SKILL.md +0 -121
  115. monoco/features/spike/resources/zh/skills/monoco_atom_spike/SKILL.md +0 -76
  116. monoco/features/spike/resources/zh/skills/monoco_workflow_research/SKILL.md +0 -121
  117. monoco_toolkit-0.3.12.dist-info/RECORD +0 -202
  118. {monoco_toolkit-0.3.12.dist-info → monoco_toolkit-0.4.0.dist-info}/WHEEL +0 -0
  119. {monoco_toolkit-0.3.12.dist-info → monoco_toolkit-0.4.0.dist-info}/entry_points.txt +0 -0
  120. {monoco_toolkit-0.3.12.dist-info → monoco_toolkit-0.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,218 +0,0 @@
1
- """
2
- RunPytestAction - Action for running pytest tests.
3
-
4
- Part of Layer 3 (Action Executor) in the event automation framework.
5
- Executes pytest and parses results.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- import asyncio
11
- import json
12
- import logging
13
- import subprocess
14
- from pathlib import Path
15
- from typing import Any, Dict, List, Optional
16
-
17
- from monoco.core.scheduler import AgentEvent
18
- from monoco.core.router import Action, ActionResult
19
-
20
- logger = logging.getLogger(__name__)
21
-
22
-
23
- class PytestResult:
24
- """Result of a pytest execution."""
25
-
26
- def __init__(
27
- self,
28
- returncode: int,
29
- stdout: str,
30
- stderr: str,
31
- summary: Optional[Dict[str, Any]] = None,
32
- ):
33
- self.returncode = returncode
34
- self.stdout = stdout
35
- self.stderr = stderr
36
- self.summary = summary or {}
37
-
38
- @property
39
- def passed(self) -> bool:
40
- return self.returncode == 0
41
-
42
- @property
43
- def failed_count(self) -> int:
44
- return self.summary.get("failed", 0)
45
-
46
- @property
47
- def passed_count(self) -> int:
48
- return self.summary.get("passed", 0)
49
-
50
- @property
51
- def total_count(self) -> int:
52
- return self.summary.get("total", 0)
53
-
54
-
55
- class RunPytestAction(Action):
56
- """
57
- Action that runs pytest tests.
58
-
59
- This action executes pytest with configurable options and
60
- parses the results for downstream processing.
61
-
62
- Example:
63
- >>> action = RunPytestAction(
64
- ... test_path="tests/",
65
- ... markers=["unit"],
66
- ... cov=True,
67
- ... )
68
- >>> result = await action(event)
69
- """
70
-
71
- def __init__(
72
- self,
73
- test_path: Optional[str] = None,
74
- markers: Optional[List[str]] = None,
75
- cov: bool = False,
76
- cov_report: Optional[str] = None,
77
- verbose: bool = True,
78
- timeout: int = 300,
79
- config: Optional[Dict[str, Any]] = None,
80
- ):
81
- super().__init__(config)
82
- self.test_path = test_path or "."
83
- self.markers = markers or []
84
- self.cov = cov
85
- self.cov_report = cov_report
86
- self.verbose = verbose
87
- self.timeout = timeout
88
- self._last_result: Optional[PytestResult] = None
89
-
90
- @property
91
- def name(self) -> str:
92
- return "RunPytestAction"
93
-
94
- async def can_execute(self, event: AgentEvent) -> bool:
95
- """Always can execute (no preconditions)."""
96
- return True
97
-
98
- async def execute(self, event: AgentEvent) -> ActionResult:
99
- """Run pytest tests."""
100
- logger.info(f"Running pytest for {self.test_path}")
101
-
102
- try:
103
- result = await self._run_pytest()
104
- self._last_result = result
105
-
106
- if result.passed:
107
- return ActionResult.success_result(
108
- output={
109
- "passed": result.passed_count,
110
- "failed": result.failed_count,
111
- "total": result.total_count,
112
- },
113
- metadata={
114
- "stdout_preview": result.stdout[:500] if result.stdout else None,
115
- },
116
- )
117
- else:
118
- return ActionResult.failure_result(
119
- error=f"Tests failed: {result.failed_count} failures",
120
- metadata={
121
- "passed": result.passed_count,
122
- "failed": result.failed_count,
123
- "total": result.total_count,
124
- "stderr_preview": result.stderr[:500] if result.stderr else None,
125
- },
126
- )
127
-
128
- except Exception as e:
129
- logger.error(f"Pytest execution failed: {e}")
130
- return ActionResult.failure_result(error=str(e))
131
-
132
- async def _run_pytest(self) -> PytestResult:
133
- """Execute pytest subprocess."""
134
- cmd = ["python", "-m", "pytest"]
135
-
136
- # Add test path
137
- cmd.append(self.test_path)
138
-
139
- # Add markers
140
- if self.markers:
141
- marker_expr = " and ".join(self.markers)
142
- cmd.extend(["-m", marker_expr])
143
-
144
- # Add coverage
145
- if self.cov:
146
- cmd.append("--cov")
147
- if self.cov_report:
148
- cmd.extend(["--cov-report", self.cov_report])
149
-
150
- # Add verbosity
151
- if self.verbose:
152
- cmd.append("-v")
153
-
154
- # Add JSON report for parsing
155
- cmd.extend(["--tb=short", "-q"])
156
-
157
- logger.debug(f"Running command: {' '.join(cmd)}")
158
-
159
- # Run subprocess
160
- process = await asyncio.create_subprocess_exec(
161
- *cmd,
162
- stdout=asyncio.subprocess.PIPE,
163
- stderr=asyncio.subprocess.PIPE,
164
- )
165
-
166
- try:
167
- stdout, stderr = await asyncio.wait_for(
168
- process.communicate(),
169
- timeout=self.timeout,
170
- )
171
- except asyncio.TimeoutError:
172
- process.kill()
173
- raise RuntimeError(f"Pytest timed out after {self.timeout}s")
174
-
175
- stdout_str = stdout.decode("utf-8", errors="replace")
176
- stderr_str = stderr.decode("utf-8", errors="replace")
177
-
178
- # Parse summary
179
- summary = self._parse_summary(stdout_str)
180
-
181
- return PytestResult(
182
- returncode=process.returncode,
183
- stdout=stdout_str,
184
- stderr=stderr_str,
185
- summary=summary,
186
- )
187
-
188
- def _parse_summary(self, output: str) -> Dict[str, int]:
189
- """Parse pytest summary from output."""
190
- summary = {"passed": 0, "failed": 0, "error": 0, "skipped": 0, "total": 0}
191
-
192
- # Look for summary line like "5 passed, 2 failed in 0.5s"
193
- import re
194
- pattern = r"(\d+)\s+(passed|failed|error|skipped)"
195
- matches = re.findall(pattern, output)
196
-
197
- for count, status in matches:
198
- summary[status] = int(count)
199
- summary["total"] += int(count)
200
-
201
- return summary
202
-
203
- def get_last_result(self) -> Optional[PytestResult]:
204
- """Get the last pytest result."""
205
- return self._last_result
206
-
207
- def get_stats(self) -> Dict[str, Any]:
208
- """Get action statistics."""
209
- stats = super().get_stats()
210
- stats.update({
211
- "test_path": self.test_path,
212
- "markers": self.markers,
213
- "last_result": {
214
- "passed": self._last_result.passed if self._last_result else None,
215
- "failed": self._last_result.failed_count if self._last_result else None,
216
- } if self._last_result else None,
217
- })
218
- return stats
@@ -1,392 +0,0 @@
1
- """
2
- ActionRouter - Layer 2 of the Event Automation Framework.
3
-
4
- This module implements the ActionRouter which routes events to appropriate actions.
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- import asyncio
10
- import inspect
11
- import logging
12
- from collections import defaultdict
13
- from typing import Any, Callable, Dict, List, Optional, Set, Union
14
-
15
- from monoco.core.scheduler import AgentEvent, AgentEventType, EventBus, event_bus
16
-
17
- from .action import Action, ActionChain, ActionRegistry, ActionResult, ActionStatus
18
-
19
- logger = logging.getLogger(__name__)
20
-
21
-
22
- class RoutingRule:
23
- """
24
- A routing rule that maps events to actions.
25
-
26
- Attributes:
27
- event_types: Event types this rule matches
28
- action: Action to execute
29
- condition: Optional additional condition
30
- priority: Rule priority (higher = executed first)
31
- """
32
-
33
- def __init__(
34
- self,
35
- event_types: List[AgentEventType],
36
- action: Union[Action, ActionChain],
37
- condition: Optional[Callable[[AgentEvent], bool]] = None,
38
- priority: int = 0,
39
- ):
40
- self.event_types = event_types
41
- self.action = action
42
- self.condition = condition
43
- self.priority = priority
44
-
45
- def matches(self, event: AgentEvent) -> bool:
46
- """Check if this rule matches the event."""
47
- if event.type not in self.event_types:
48
- return False
49
-
50
- if self.condition is not None:
51
- if inspect.iscoroutinefunction(self.condition):
52
- # Note: This is called from sync context, so we can't await
53
- # For async conditions, use Action.can_execute instead
54
- logger.warning("Async conditions in RoutingRule are not supported")
55
- return False
56
- return self.condition(event)
57
-
58
- return True
59
-
60
-
61
- class ActionRouter:
62
- """
63
- Event router that maps events to actions (Layer 2).
64
-
65
- Responsibilities:
66
- - Subscribe to EventBus events
67
- - Route events to registered actions
68
- - Support conditional routing
69
- - Support action chains
70
- - Manage action lifecycle
71
-
72
- Example:
73
- >>> router = ActionRouter()
74
- >>>
75
- >>> # Register simple action
76
- >>> router.register(AgentEventType.ISSUE_CREATED, my_action)
77
- >>>
78
- >>> # Register with condition
79
- >>> router.register(
80
- ... AgentEventType.ISSUE_STAGE_CHANGED,
81
- ... engineer_action,
82
- ... condition=lambda e: e.payload.get("new_stage") == "doing"
83
- ... )
84
- >>>
85
- >>> await router.start()
86
- """
87
-
88
- def __init__(
89
- self,
90
- event_bus: Optional[EventBus] = None,
91
- name: str = "ActionRouter",
92
- ):
93
- self.event_bus = event_bus or event_bus
94
- self.name = name
95
- self._rules: List[RoutingRule] = []
96
- self._registry = ActionRegistry()
97
- self._running = False
98
- self._event_handler: Optional[Callable[[AgentEvent], Any]] = None
99
-
100
- # Statistics
101
- self._event_count = 0
102
- self._routed_count = 0
103
- self._execution_results: List[ActionResult] = []
104
- self._max_results_history = 100
105
-
106
- def register(
107
- self,
108
- event_types: Union[AgentEventType, List[AgentEventType]],
109
- action: Union[Action, ActionChain],
110
- condition: Optional[Callable[[AgentEvent], bool]] = None,
111
- priority: int = 0,
112
- ) -> "ActionRouter":
113
- """
114
- Register an action for event types.
115
-
116
- Args:
117
- event_types: Event type(s) to subscribe to
118
- action: Action or ActionChain to execute
119
- condition: Optional condition function
120
- priority: Rule priority (higher = executed first)
121
-
122
- Returns:
123
- Self for chaining
124
- """
125
- if isinstance(event_types, AgentEventType):
126
- event_types = [event_types]
127
-
128
- rule = RoutingRule(
129
- event_types=event_types,
130
- action=action,
131
- condition=condition,
132
- priority=priority,
133
- )
134
-
135
- self._rules.append(rule)
136
-
137
- # Sort rules by priority (descending)
138
- self._rules.sort(key=lambda r: r.priority, reverse=True)
139
-
140
- # Register action in registry
141
- if isinstance(action, Action):
142
- self._registry.register(action)
143
- elif isinstance(action, ActionChain):
144
- for a in action.actions:
145
- self._registry.register(a)
146
-
147
- logger.info(
148
- f"Registered {action.name if hasattr(action, 'name') else type(action).__name__} "
149
- f"for events: {[t.value for t in event_types]}"
150
- )
151
-
152
- return self
153
-
154
- def unregister(self, action_name: str) -> bool:
155
- """
156
- Unregister an action by name.
157
-
158
- Args:
159
- action_name: Name of the action to unregister
160
-
161
- Returns:
162
- True if action was found and removed
163
- """
164
- # Remove from rules
165
- original_count = len(self._rules)
166
- self._rules = [
167
- r for r in self._rules
168
- if not (
169
- (hasattr(r.action, 'name') and r.action.name == action_name) or
170
- (isinstance(r.action, ActionChain) and action_name in [a.name for a in r.action.actions])
171
- )
172
- ]
173
-
174
- # Remove from registry
175
- self._registry.unregister(action_name)
176
-
177
- removed = len(self._rules) < original_count
178
- if removed:
179
- logger.info(f"Unregistered action: {action_name}")
180
-
181
- return removed
182
-
183
- async def start(self) -> None:
184
- """Start the router and subscribe to events."""
185
- if self._running:
186
- return
187
-
188
- self._running = True
189
-
190
- # Create event handler
191
- self._event_handler = self._handle_event
192
-
193
- # Subscribe to all event types mentioned in rules
194
- event_types = set()
195
- for rule in self._rules:
196
- event_types.update(rule.event_types)
197
-
198
- for event_type in event_types:
199
- self.event_bus.subscribe(event_type, self._event_handler)
200
-
201
- logger.info(f"Started ActionRouter with {len(self._rules)} rules")
202
-
203
- async def stop(self) -> None:
204
- """Stop the router and unsubscribe from events."""
205
- if not self._running:
206
- return
207
-
208
- self._running = False
209
-
210
- # Unsubscribe from all event types
211
- if self._event_handler:
212
- for event_type in AgentEventType:
213
- self.event_bus.unsubscribe(event_type, self._event_handler)
214
-
215
- logger.info("Stopped ActionRouter")
216
-
217
- async def _handle_event(self, event: AgentEvent) -> None:
218
- """
219
- Handle incoming events.
220
-
221
- Args:
222
- event: The event to route
223
- """
224
- self._event_count += 1
225
-
226
- logger.debug(f"Routing event: {event.type.value}")
227
-
228
- # Find matching rules
229
- matching_rules = [r for r in self._rules if r.matches(event)]
230
-
231
- if not matching_rules:
232
- logger.debug(f"No matching rules for event: {event.type.value}")
233
- return
234
-
235
- # Execute actions
236
- for rule in matching_rules:
237
- try:
238
- if isinstance(rule.action, ActionChain):
239
- results = await rule.action.execute(event)
240
- for result in results:
241
- self._record_result(result)
242
- else:
243
- result = await rule.action(event)
244
- self._record_result(result)
245
-
246
- self._routed_count += 1
247
-
248
- except Exception as e:
249
- logger.error(f"Error executing action for {event.type.value}: {e}")
250
- self._record_result(ActionResult.failure_result(error=str(e)))
251
-
252
- def _record_result(self, result: ActionResult) -> None:
253
- """Record execution result."""
254
- self._execution_results.append(result)
255
-
256
- # Trim history
257
- if len(self._execution_results) > self._max_results_history:
258
- self._execution_results = self._execution_results[-self._max_results_history:]
259
-
260
- def route(self, event: AgentEvent) -> List[ActionResult]:
261
- """
262
- Manually route an event (synchronous version).
263
-
264
- Args:
265
- event: The event to route
266
-
267
- Returns:
268
- List of action results
269
- """
270
- results = []
271
- matching_rules = [r for r in self._rules if r.matches(event)]
272
-
273
- for rule in matching_rules:
274
- try:
275
- if isinstance(rule.action, ActionChain):
276
- # For chains, we need to run async
277
- loop = asyncio.get_event_loop()
278
- chain_results = loop.run_until_complete(rule.action.execute(event))
279
- results.extend(chain_results)
280
- else:
281
- loop = asyncio.get_event_loop()
282
- result = loop.run_until_complete(rule.action(event))
283
- results.append(result)
284
-
285
- except Exception as e:
286
- logger.error(f"Error in manual route: {e}")
287
- results.append(ActionResult.failure_result(error=str(e)))
288
-
289
- return results
290
-
291
- def get_rules(self) -> List[Dict[str, Any]]:
292
- """Get all routing rules as dicts."""
293
- return [
294
- {
295
- "event_types": [t.value for t in r.event_types],
296
- "action": r.action.name if hasattr(r.action, 'name') else str(type(r.action)),
297
- "priority": r.priority,
298
- "has_condition": r.condition is not None,
299
- }
300
- for r in self._rules
301
- ]
302
-
303
- def get_stats(self) -> Dict[str, Any]:
304
- """Get router statistics."""
305
- success_count = sum(
306
- 1 for r in self._execution_results
307
- if r.success and r.status == ActionStatus.SUCCESS
308
- )
309
- failed_count = sum(
310
- 1 for r in self._execution_results
311
- if not r.success
312
- )
313
- skipped_count = sum(
314
- 1 for r in self._execution_results
315
- if r.status == ActionStatus.SKIPPED
316
- )
317
-
318
- return {
319
- "name": self.name,
320
- "running": self._running,
321
- "rules": len(self._rules),
322
- "registered_actions": self._registry.list_actions(),
323
- "events_received": self._event_count,
324
- "events_routed": self._routed_count,
325
- "results": {
326
- "total": len(self._execution_results),
327
- "success": success_count,
328
- "failed": failed_count,
329
- "skipped": skipped_count,
330
- },
331
- }
332
-
333
-
334
- class ConditionalRouter(ActionRouter):
335
- """
336
- Router with advanced conditional routing capabilities.
337
-
338
- Supports complex routing logic based on event payload.
339
- """
340
-
341
- def register_field_condition(
342
- self,
343
- event_types: Union[AgentEventType, List[AgentEventType]],
344
- action: Union[Action, ActionChain],
345
- field: str,
346
- expected_value: Any,
347
- priority: int = 0,
348
- ) -> "ConditionalRouter":
349
- """
350
- Register action with a field value condition.
351
-
352
- Args:
353
- event_types: Event type(s) to subscribe to
354
- action: Action to execute
355
- field: Payload field to check
356
- expected_value: Expected value of the field
357
- priority: Rule priority
358
-
359
- Returns:
360
- Self for chaining
361
- """
362
- def condition(event: AgentEvent) -> bool:
363
- return event.payload.get(field) == expected_value
364
-
365
- return self.register(event_types, action, condition, priority)
366
-
367
- def register_payload_condition(
368
- self,
369
- event_types: Union[AgentEventType, List[AgentEventType]],
370
- action: Union[Action, ActionChain],
371
- payload_matcher: Dict[str, Any],
372
- priority: int = 0,
373
- ) -> "ConditionalRouter":
374
- """
375
- Register action with a payload matching condition.
376
-
377
- Args:
378
- event_types: Event type(s) to subscribe to
379
- action: Action to execute
380
- payload_matcher: Dict of field -> expected value
381
- priority: Rule priority
382
-
383
- Returns:
384
- Self for chaining
385
- """
386
- def condition(event: AgentEvent) -> bool:
387
- return all(
388
- event.payload.get(k) == v
389
- for k, v in payload_matcher.items()
390
- )
391
-
392
- return self.register(event_types, action, condition, priority)
@@ -1,61 +0,0 @@
1
- ---
2
- name: atom-code-dev
3
- type: atom
4
- domain: code
5
- description: 代码开发的原子操作 - 调研、实现、测试、文档
6
- version: 1.0.0
7
- author: Monoco Toolkit
8
-
9
- # 系统级合规规则
10
- compliance_rules:
11
- - rule: "遵循项目代码规范"
12
- severity: warning
13
-
14
- - rule: "优先编写测试"
15
- severity: warning
16
- mindset: "TDD"
17
-
18
- - rule: "保持代码简单直观"
19
- severity: info
20
- mindset: "KISS"
21
-
22
- # 原子操作定义
23
- operations:
24
- - name: investigate
25
- description: 理解需求与上下文,识别相关代码和依赖
26
- reminder: "理解需求后再开始编码,识别相关文件和依赖 Issue"
27
- checkpoints:
28
- - "阅读并理解 Issue 描述"
29
- - "识别相关代码文件"
30
- - "检查依赖 Issue 状态"
31
- - "评估技术可行性"
32
- output: "技术方案草稿、风险清单"
33
-
34
- - name: implement
35
- description: 实现代码变更
36
- reminder: "遵循项目代码规范,小步提交,处理边界情况"
37
- checkpoints:
38
- - "遵循项目代码规范"
39
- - "编写/更新必要的文档"
40
- - "处理边界情况"
41
- - "保持代码可审查性(单次提交 < 400 行)"
42
-
43
- - name: test
44
- description: 运行测试确保代码质量
45
- reminder: "所有测试必须通过,检查测试覆盖率"
46
- checkpoints:
47
- - "编写/更新单元测试"
48
- - "运行测试套件 (pytest, cargo test, etc.)"
49
- - "修复失败的测试"
50
- - "检查测试覆盖率"
51
- compliance_rules:
52
- - rule: "所有单元测试必须通过后才能提交"
53
- severity: error
54
-
55
- - name: document
56
- description: 更新文档
57
- reminder: "代码变更必须同步更新文档"
58
- checkpoints:
59
- - "更新代码注释"
60
- - "更新相关文档"
61
- - "更新 CHANGELOG(如适用)"