monoco-toolkit 0.3.11__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 (132) hide show
  1. monoco/core/automation/__init__.py +40 -0
  2. monoco/core/automation/field_watcher.py +296 -0
  3. monoco/core/automation/handlers.py +805 -0
  4. monoco/core/config.py +29 -11
  5. monoco/core/daemon/__init__.py +5 -0
  6. monoco/core/daemon/pid.py +290 -0
  7. monoco/core/git.py +15 -0
  8. monoco/core/hooks/context.py +74 -13
  9. monoco/core/injection.py +86 -8
  10. monoco/core/integrations.py +0 -24
  11. monoco/core/router/__init__.py +17 -0
  12. monoco/core/router/action.py +202 -0
  13. monoco/core/scheduler/__init__.py +63 -0
  14. monoco/core/scheduler/base.py +152 -0
  15. monoco/core/scheduler/engines.py +175 -0
  16. monoco/core/scheduler/events.py +197 -0
  17. monoco/core/scheduler/local.py +377 -0
  18. monoco/core/setup.py +9 -0
  19. monoco/core/sync.py +199 -4
  20. monoco/core/watcher/__init__.py +63 -0
  21. monoco/core/watcher/base.py +382 -0
  22. monoco/core/watcher/dropzone.py +152 -0
  23. monoco/core/watcher/im.py +460 -0
  24. monoco/core/watcher/issue.py +303 -0
  25. monoco/core/watcher/memo.py +192 -0
  26. monoco/core/watcher/task.py +238 -0
  27. monoco/daemon/app.py +3 -60
  28. monoco/daemon/commands.py +459 -25
  29. monoco/daemon/events.py +34 -0
  30. monoco/daemon/scheduler.py +157 -201
  31. monoco/daemon/services.py +42 -243
  32. monoco/features/agent/__init__.py +25 -7
  33. monoco/features/agent/cli.py +91 -57
  34. monoco/features/agent/engines.py +31 -170
  35. monoco/features/agent/resources/en/AGENTS.md +14 -14
  36. monoco/features/agent/resources/en/skills/monoco_role_engineer/SKILL.md +101 -0
  37. monoco/features/agent/resources/en/skills/monoco_role_manager/SKILL.md +95 -0
  38. monoco/features/agent/resources/en/skills/monoco_role_planner/SKILL.md +177 -0
  39. monoco/features/agent/resources/en/skills/monoco_role_reviewer/SKILL.md +139 -0
  40. monoco/features/agent/resources/zh/skills/monoco_role_engineer/SKILL.md +101 -0
  41. monoco/features/agent/resources/zh/skills/monoco_role_manager/SKILL.md +95 -0
  42. monoco/features/agent/resources/zh/skills/monoco_role_planner/SKILL.md +177 -0
  43. monoco/features/agent/resources/zh/skills/monoco_role_reviewer/SKILL.md +139 -0
  44. monoco/features/agent/worker.py +1 -1
  45. monoco/features/hooks/__init__.py +61 -6
  46. monoco/features/hooks/commands.py +281 -271
  47. monoco/features/hooks/dispatchers/__init__.py +23 -0
  48. monoco/features/hooks/dispatchers/agent_dispatcher.py +486 -0
  49. monoco/features/hooks/dispatchers/git_dispatcher.py +478 -0
  50. monoco/features/hooks/manager.py +357 -0
  51. monoco/features/hooks/models.py +262 -0
  52. monoco/features/hooks/parser.py +322 -0
  53. monoco/features/hooks/universal_interceptor.py +503 -0
  54. monoco/features/im/__init__.py +67 -0
  55. monoco/features/im/core.py +782 -0
  56. monoco/features/im/models.py +311 -0
  57. monoco/features/issue/commands.py +133 -60
  58. monoco/features/issue/core.py +385 -40
  59. monoco/features/issue/domain_commands.py +0 -19
  60. monoco/features/issue/resources/en/AGENTS.md +17 -122
  61. monoco/features/issue/resources/hooks/agent/before-tool.sh +102 -0
  62. monoco/features/issue/resources/hooks/agent/session-start.sh +88 -0
  63. monoco/features/issue/resources/hooks/{post-checkout.sh → git/git-post-checkout.sh} +10 -9
  64. monoco/features/issue/resources/hooks/git/git-pre-commit.sh +31 -0
  65. monoco/features/issue/resources/hooks/{pre-push.sh → git/git-pre-push.sh} +7 -13
  66. monoco/features/issue/resources/zh/AGENTS.md +18 -123
  67. monoco/features/memo/cli.py +15 -64
  68. monoco/features/memo/core.py +6 -34
  69. monoco/features/memo/models.py +24 -15
  70. monoco/features/memo/resources/en/AGENTS.md +31 -0
  71. monoco/features/memo/resources/zh/AGENTS.md +28 -5
  72. monoco/features/spike/commands.py +5 -3
  73. monoco/main.py +5 -3
  74. {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.4.0.dist-info}/METADATA +1 -1
  75. monoco_toolkit-0.4.0.dist-info/RECORD +170 -0
  76. monoco/core/execution.py +0 -67
  77. monoco/features/agent/apoptosis.py +0 -44
  78. monoco/features/agent/manager.py +0 -127
  79. monoco/features/agent/resources/atoms/atom-code-dev.yaml +0 -61
  80. monoco/features/agent/resources/atoms/atom-issue-lifecycle.yaml +0 -73
  81. monoco/features/agent/resources/atoms/atom-knowledge.yaml +0 -55
  82. monoco/features/agent/resources/atoms/atom-review.yaml +0 -60
  83. monoco/features/agent/resources/en/skills/monoco_atom_core/SKILL.md +0 -99
  84. monoco/features/agent/resources/en/skills/monoco_workflow_agent_engineer/SKILL.md +0 -94
  85. monoco/features/agent/resources/en/skills/monoco_workflow_agent_manager/SKILL.md +0 -93
  86. monoco/features/agent/resources/en/skills/monoco_workflow_agent_planner/SKILL.md +0 -85
  87. monoco/features/agent/resources/en/skills/monoco_workflow_agent_reviewer/SKILL.md +0 -114
  88. monoco/features/agent/resources/workflows/workflow-dev.yaml +0 -83
  89. monoco/features/agent/resources/workflows/workflow-issue-create.yaml +0 -72
  90. monoco/features/agent/resources/workflows/workflow-review.yaml +0 -94
  91. monoco/features/agent/resources/zh/roles/monoco_role_engineer.yaml +0 -49
  92. monoco/features/agent/resources/zh/roles/monoco_role_manager.yaml +0 -46
  93. monoco/features/agent/resources/zh/roles/monoco_role_planner.yaml +0 -46
  94. monoco/features/agent/resources/zh/roles/monoco_role_reviewer.yaml +0 -47
  95. monoco/features/agent/resources/zh/skills/monoco_atom_core/SKILL.md +0 -99
  96. monoco/features/agent/resources/zh/skills/monoco_workflow_agent_engineer/SKILL.md +0 -94
  97. monoco/features/agent/resources/zh/skills/monoco_workflow_agent_manager/SKILL.md +0 -88
  98. monoco/features/agent/resources/zh/skills/monoco_workflow_agent_planner/SKILL.md +0 -259
  99. monoco/features/agent/resources/zh/skills/monoco_workflow_agent_reviewer/SKILL.md +0 -137
  100. monoco/features/agent/session.py +0 -169
  101. monoco/features/artifact/resources/zh/skills/monoco_atom_artifact/SKILL.md +0 -278
  102. monoco/features/glossary/resources/en/skills/monoco_atom_glossary/SKILL.md +0 -35
  103. monoco/features/glossary/resources/zh/skills/monoco_atom_glossary/SKILL.md +0 -35
  104. monoco/features/hooks/adapter.py +0 -67
  105. monoco/features/hooks/core.py +0 -441
  106. monoco/features/i18n/resources/en/skills/monoco_atom_i18n/SKILL.md +0 -96
  107. monoco/features/i18n/resources/en/skills/monoco_workflow_i18n_scan/SKILL.md +0 -105
  108. monoco/features/i18n/resources/zh/skills/monoco_atom_i18n/SKILL.md +0 -96
  109. monoco/features/i18n/resources/zh/skills/monoco_workflow_i18n_scan/SKILL.md +0 -105
  110. monoco/features/issue/resources/en/skills/monoco_atom_issue/SKILL.md +0 -165
  111. monoco/features/issue/resources/en/skills/monoco_workflow_issue_creation/SKILL.md +0 -167
  112. monoco/features/issue/resources/en/skills/monoco_workflow_issue_development/SKILL.md +0 -224
  113. monoco/features/issue/resources/en/skills/monoco_workflow_issue_management/SKILL.md +0 -159
  114. monoco/features/issue/resources/en/skills/monoco_workflow_issue_refinement/SKILL.md +0 -203
  115. monoco/features/issue/resources/hooks/pre-commit.sh +0 -41
  116. monoco/features/issue/resources/zh/skills/monoco_atom_issue_lifecycle/SKILL.md +0 -190
  117. monoco/features/issue/resources/zh/skills/monoco_workflow_issue_creation/SKILL.md +0 -167
  118. monoco/features/issue/resources/zh/skills/monoco_workflow_issue_development/SKILL.md +0 -224
  119. monoco/features/issue/resources/zh/skills/monoco_workflow_issue_management/SKILL.md +0 -159
  120. monoco/features/issue/resources/zh/skills/monoco_workflow_issue_refinement/SKILL.md +0 -203
  121. monoco/features/memo/resources/en/skills/monoco_atom_memo/SKILL.md +0 -77
  122. monoco/features/memo/resources/en/skills/monoco_workflow_note_processing/SKILL.md +0 -140
  123. monoco/features/memo/resources/zh/skills/monoco_atom_memo/SKILL.md +0 -77
  124. monoco/features/memo/resources/zh/skills/monoco_workflow_note_processing/SKILL.md +0 -140
  125. monoco/features/spike/resources/en/skills/monoco_atom_spike/SKILL.md +0 -76
  126. monoco/features/spike/resources/en/skills/monoco_workflow_research/SKILL.md +0 -121
  127. monoco/features/spike/resources/zh/skills/monoco_atom_spike/SKILL.md +0 -76
  128. monoco/features/spike/resources/zh/skills/monoco_workflow_research/SKILL.md +0 -121
  129. monoco_toolkit-0.3.11.dist-info/RECORD +0 -181
  130. {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.4.0.dist-info}/WHEEL +0 -0
  131. {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.4.0.dist-info}/entry_points.txt +0 -0
  132. {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,238 @@
1
+ """
2
+ TaskWatcher - Monitors task files for changes.
3
+
4
+ Part of Layer 1 (File Watcher) in the event automation framework.
5
+ Emits events for task status changes and completion.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ import logging
12
+ import re
13
+ from dataclasses import dataclass
14
+ from pathlib import Path
15
+ from typing import Any, Dict, List, Optional, Set
16
+
17
+ from monoco.core.scheduler import AgentEventType, EventBus, event_bus
18
+
19
+ from .base import (
20
+ ChangeType,
21
+ FieldChange,
22
+ FileEvent,
23
+ FilesystemWatcher,
24
+ WatchConfig,
25
+ PollingWatcher,
26
+ )
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ class TaskFileEvent(FileEvent):
32
+ """FileEvent specific to Task files."""
33
+
34
+ def __init__(
35
+ self,
36
+ path: Path,
37
+ change_type: ChangeType,
38
+ task_changes: Optional[List[Dict[str, Any]]] = None,
39
+ **kwargs,
40
+ ):
41
+ super().__init__(
42
+ path=path,
43
+ change_type=change_type,
44
+ watcher_name="TaskWatcher",
45
+ **kwargs,
46
+ )
47
+ self.task_changes = task_changes or []
48
+
49
+ def to_agent_event_type(self) -> Optional[AgentEventType]:
50
+ """Tasks map to issue updates for now."""
51
+ return AgentEventType.ISSUE_UPDATED
52
+
53
+ def to_payload(self) -> Dict[str, Any]:
54
+ """Convert to payload with Task-specific fields."""
55
+ payload = super().to_payload()
56
+ payload["task_changes"] = self.task_changes
57
+ return payload
58
+
59
+
60
+ @dataclass
61
+ class TaskItem:
62
+ """Represents a single task item."""
63
+ content: str
64
+ state: str # " ", "x", "X", "-", "/"
65
+ line_number: int
66
+ level: int = 0
67
+
68
+ @property
69
+ def is_completed(self) -> bool:
70
+ return self.state.lower() == "x"
71
+
72
+ @property
73
+ def is_in_progress(self) -> bool:
74
+ return self.state in ("-", "/")
75
+
76
+
77
+ class TaskWatcher(PollingWatcher):
78
+ """
79
+ Watcher for task files.
80
+
81
+ Monitors task files (e.g., tasks.md, TODO.md) for:
82
+ - Task creation
83
+ - Task status changes (todo -> doing -> done)
84
+ - Task completion
85
+
86
+ Example:
87
+ >>> config = WatchConfig(
88
+ ... path=Path("./tasks.md"),
89
+ ... patterns=["*.md"],
90
+ ... )
91
+ >>> watcher = TaskWatcher(config)
92
+ >>> await watcher.start()
93
+ """
94
+
95
+ # Regex to match task items
96
+ TASK_PATTERN = re.compile(
97
+ r"^(\s*)-\s*\[([ xX\-/])\]\s*(.+)$",
98
+ re.MULTILINE,
99
+ )
100
+
101
+ def __init__(
102
+ self,
103
+ config: WatchConfig,
104
+ event_bus: Optional[EventBus] = None,
105
+ name: str = "TaskWatcher",
106
+ ):
107
+ super().__init__(config, event_bus, name)
108
+ self._task_cache: Dict[str, TaskItem] = {} # task_id -> TaskItem
109
+
110
+ async def _check_changes(self) -> None:
111
+ """Check for task file changes."""
112
+ if not self.config.path.exists():
113
+ return
114
+
115
+ try:
116
+ content = self._read_file_content(self.config.path) or ""
117
+ current_tasks = self._parse_tasks(content)
118
+
119
+ # Detect changes
120
+ task_changes = self._detect_task_changes(current_tasks)
121
+
122
+ if task_changes:
123
+ await self._emit_task_changes(task_changes)
124
+
125
+ # Update cache
126
+ self._task_cache = current_tasks
127
+
128
+ except Exception as e:
129
+ logger.error(f"Error checking task file: {e}")
130
+
131
+ def _parse_tasks(self, content: str) -> Dict[str, TaskItem]:
132
+ """Parse task items from content."""
133
+ tasks = {}
134
+ lines = content.split("\n")
135
+
136
+ for line_num, line in enumerate(lines, 1):
137
+ match = self.TASK_PATTERN.match(line)
138
+ if match:
139
+ indent = match.group(1)
140
+ state = match.group(2)
141
+ task_content = match.group(3).strip()
142
+
143
+ # Generate task ID from content hash
144
+ import hashlib
145
+ task_id = hashlib.md5(
146
+ f"{line_num}:{task_content}".encode()
147
+ ).hexdigest()[:12]
148
+
149
+ tasks[task_id] = TaskItem(
150
+ content=task_content,
151
+ state=state,
152
+ line_number=line_num,
153
+ level=len(indent) // 2,
154
+ )
155
+
156
+ return tasks
157
+
158
+ def _detect_task_changes(
159
+ self,
160
+ current_tasks: Dict[str, TaskItem],
161
+ ) -> List[Dict[str, Any]]:
162
+ """Detect changes between cached and current tasks."""
163
+ changes = []
164
+ current_ids = set(current_tasks.keys())
165
+ cached_ids = set(self._task_cache.keys())
166
+
167
+ # New tasks
168
+ for task_id in current_ids - cached_ids:
169
+ task = current_tasks[task_id]
170
+ changes.append({
171
+ "type": "created",
172
+ "task_id": task_id,
173
+ "content": task.content,
174
+ "state": task.state,
175
+ })
176
+
177
+ # Deleted tasks
178
+ for task_id in cached_ids - current_ids:
179
+ task = self._task_cache[task_id]
180
+ changes.append({
181
+ "type": "deleted",
182
+ "task_id": task_id,
183
+ "content": task.content,
184
+ })
185
+
186
+ # Modified tasks
187
+ for task_id in current_ids & cached_ids:
188
+ current = current_tasks[task_id]
189
+ cached = self._task_cache[task_id]
190
+
191
+ if current.state != cached.state:
192
+ changes.append({
193
+ "type": "state_changed",
194
+ "task_id": task_id,
195
+ "content": current.content,
196
+ "old_state": cached.state,
197
+ "new_state": current.state,
198
+ "is_completed": current.is_completed,
199
+ })
200
+
201
+ return changes
202
+
203
+ async def _emit_task_changes(self, changes: List[Dict[str, Any]]) -> None:
204
+ """Emit events for task changes."""
205
+ event = TaskFileEvent(
206
+ path=self.config.path,
207
+ change_type=ChangeType.MODIFIED,
208
+ task_changes=changes,
209
+ metadata={
210
+ "total_changes": len(changes),
211
+ "completed_tasks": sum(1 for c in changes if c.get("is_completed")),
212
+ },
213
+ )
214
+ await self.emit(event)
215
+
216
+ # Log summary
217
+ created = sum(1 for c in changes if c["type"] == "created")
218
+ completed = sum(1 for c in changes if c["type"] == "state_changed" and c.get("is_completed"))
219
+ logger.debug(f"Task changes: {created} created, {completed} completed")
220
+
221
+ def get_task_stats(self) -> Dict[str, int]:
222
+ """Get task statistics."""
223
+ total = len(self._task_cache)
224
+ completed = sum(1 for t in self._task_cache.values() if t.is_completed)
225
+ in_progress = sum(1 for t in self._task_cache.values() if t.is_in_progress)
226
+
227
+ return {
228
+ "total": total,
229
+ "completed": completed,
230
+ "in_progress": in_progress,
231
+ "pending": total - completed - in_progress,
232
+ }
233
+
234
+ def get_stats(self) -> Dict[str, Any]:
235
+ """Get watcher statistics."""
236
+ stats = super().get_stats()
237
+ stats.update(self.get_task_stats())
238
+ return stats
monoco/daemon/app.py CHANGED
@@ -5,10 +5,10 @@ from sse_starlette.sse import EventSourceResponse
5
5
  import asyncio
6
6
  import logging
7
7
  import os
8
- from typing import Optional, List, Dict
8
+ from typing import Optional, Dict
9
9
  from monoco.daemon.services import Broadcaster, ProjectManager
10
10
  from monoco.core.git import GitMonitor
11
- from monoco.core.config import get_config, ConfigMonitor, ConfigScope, get_config_path
11
+ from monoco.core.config import get_config
12
12
  from monoco.daemon.scheduler import SchedulerService
13
13
  from monoco.daemon.mailroom_service import MailroomService
14
14
 
@@ -18,11 +18,6 @@ logger = logging.getLogger("monoco.daemon")
18
18
  from pathlib import Path
19
19
  from monoco.core.config import get_config
20
20
  from monoco.features.issue.core import list_issues
21
- from monoco.core.execution import (
22
- scan_execution_profiles,
23
- get_profile_detail,
24
- ExecutionProfile,
25
- )
26
21
 
27
22
  description = """
28
23
  Monoco Daemon Process
@@ -34,7 +29,6 @@ Monoco Daemon Process
34
29
  # Service Instances
35
30
  broadcaster = Broadcaster()
36
31
  git_monitor: GitMonitor | None = None
37
- config_monitors: List[ConfigMonitor] = []
38
32
  project_manager: ProjectManager | None = None
39
33
  scheduler_service: SchedulerService | None = None
40
34
  mailroom_service: MailroomService | None = None
@@ -45,7 +39,7 @@ async def lifespan(app: FastAPI):
45
39
  # Startup
46
40
  logger.info("Starting Monoco Daemon services...")
47
41
 
48
- global project_manager, git_monitor, config_monitors, scheduler_service, mailroom_service
42
+ global project_manager, git_monitor, scheduler_service, mailroom_service
49
43
  # Use MONOCO_SERVER_ROOT if set, otherwise CWD
50
44
  env_root = os.getenv("MONOCO_SERVER_ROOT")
51
45
  workspace_root = Path(env_root) if env_root else Path.cwd()
@@ -55,26 +49,8 @@ async def lifespan(app: FastAPI):
55
49
  async def on_git_change(new_hash: str):
56
50
  await broadcaster.broadcast("HEAD_UPDATED", {"ref": "HEAD", "hash": new_hash})
57
51
 
58
- async def on_config_change(path: str):
59
- logger.info(f"Config file changed: {path}, broadcasting update...")
60
- await broadcaster.broadcast(
61
- "CONFIG_UPDATED", {"scope": "workspace", "path": path}
62
- )
63
-
64
52
  git_monitor = GitMonitor(workspace_root, on_git_change)
65
53
 
66
- project_config_path = get_config_path(ConfigScope.PROJECT, str(workspace_root))
67
- workspace_config_path = get_config_path(ConfigScope.WORKSPACE, str(workspace_root))
68
-
69
- config_monitors = [
70
- ConfigMonitor(
71
- project_config_path, lambda: on_config_change(str(project_config_path))
72
- ),
73
- ConfigMonitor(
74
- workspace_config_path, lambda: on_config_change(str(workspace_config_path))
75
- ),
76
- ]
77
-
78
54
  await project_manager.start_all()
79
55
  # Start Scheduler
80
56
  scheduler_service = SchedulerService(project_manager)
@@ -88,15 +64,12 @@ async def lifespan(app: FastAPI):
88
64
  await mailroom_service.start()
89
65
 
90
66
  git_task = asyncio.create_task(git_monitor.start())
91
- config_tasks = [asyncio.create_task(m.start()) for m in config_monitors]
92
67
 
93
68
  yield
94
69
  # Shutdown
95
70
  logger.info("Shutting down Monoco Daemon services...")
96
71
  if git_monitor:
97
72
  git_monitor.stop()
98
- for m in config_monitors:
99
- m.stop()
100
73
  if project_manager:
101
74
  project_manager.stop_all()
102
75
  if scheduler_service:
@@ -105,7 +78,6 @@ async def lifespan(app: FastAPI):
105
78
  await mailroom_service.stop()
106
79
 
107
80
  await git_task
108
- await asyncio.gather(*config_tasks)
109
81
 
110
82
 
111
83
  app = FastAPI(
@@ -463,35 +435,6 @@ async def refresh_monitor():
463
435
  return {"status": "refreshed", "head": current_hash}
464
436
 
465
437
 
466
- # --- Execution Profiles ---
467
-
468
-
469
- @app.get("/api/v1/execution/profiles", response_model=List[ExecutionProfile])
470
- async def get_execution_profiles(project_id: Optional[str] = None):
471
- """
472
- List all execution profiles available for the project/workspace.
473
- """
474
- project = None
475
- if project_id:
476
- project = get_project_or_404(project_id)
477
- elif project_manager and project_manager.projects:
478
- # Fallback to first project if none specified
479
- project = list(project_manager.projects.values())[0]
480
-
481
- return scan_execution_profiles(project.path if project else None)
482
-
483
-
484
- @app.get("/api/v1/execution/profiles/detail", response_model=ExecutionProfile)
485
- async def get_execution_profile_detail(path: str):
486
- """
487
- Get full content of an execution profile.
488
- """
489
- profile = get_profile_detail(path)
490
- if not profile:
491
- raise HTTPException(status_code=404, detail="Profile not found")
492
- return profile
493
-
494
-
495
438
  # --- Workspace State Management ---
496
439
  from monoco.core.state import WorkspaceState
497
440