monoco-toolkit 0.3.10__py3-none-any.whl → 0.3.12__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 (130) hide show
  1. monoco/__main__.py +8 -0
  2. monoco/core/artifacts/__init__.py +16 -0
  3. monoco/core/artifacts/manager.py +575 -0
  4. monoco/core/artifacts/models.py +161 -0
  5. monoco/core/automation/__init__.py +51 -0
  6. monoco/core/automation/config.py +338 -0
  7. monoco/core/automation/field_watcher.py +296 -0
  8. monoco/core/automation/handlers.py +723 -0
  9. monoco/core/config.py +31 -4
  10. monoco/core/executor/__init__.py +38 -0
  11. monoco/core/executor/agent_action.py +254 -0
  12. monoco/core/executor/git_action.py +303 -0
  13. monoco/core/executor/im_action.py +309 -0
  14. monoco/core/executor/pytest_action.py +218 -0
  15. monoco/core/git.py +38 -0
  16. monoco/core/hooks/context.py +74 -13
  17. monoco/core/ingestion/__init__.py +20 -0
  18. monoco/core/ingestion/discovery.py +248 -0
  19. monoco/core/ingestion/watcher.py +343 -0
  20. monoco/core/ingestion/worker.py +436 -0
  21. monoco/core/loader.py +633 -0
  22. monoco/core/registry.py +34 -25
  23. monoco/core/router/__init__.py +55 -0
  24. monoco/core/router/action.py +341 -0
  25. monoco/core/router/router.py +392 -0
  26. monoco/core/scheduler/__init__.py +63 -0
  27. monoco/core/scheduler/base.py +152 -0
  28. monoco/core/scheduler/engines.py +175 -0
  29. monoco/core/scheduler/events.py +171 -0
  30. monoco/core/scheduler/local.py +377 -0
  31. monoco/core/skills.py +119 -80
  32. monoco/core/watcher/__init__.py +57 -0
  33. monoco/core/watcher/base.py +365 -0
  34. monoco/core/watcher/dropzone.py +152 -0
  35. monoco/core/watcher/issue.py +303 -0
  36. monoco/core/watcher/memo.py +200 -0
  37. monoco/core/watcher/task.py +238 -0
  38. monoco/daemon/app.py +77 -1
  39. monoco/daemon/commands.py +10 -0
  40. monoco/daemon/events.py +34 -0
  41. monoco/daemon/mailroom_service.py +196 -0
  42. monoco/daemon/models.py +1 -0
  43. monoco/daemon/scheduler.py +207 -0
  44. monoco/daemon/services.py +27 -58
  45. monoco/daemon/triggers.py +55 -0
  46. monoco/features/agent/__init__.py +25 -7
  47. monoco/features/agent/adapter.py +17 -7
  48. monoco/features/agent/cli.py +91 -57
  49. monoco/features/agent/engines.py +31 -170
  50. monoco/{core/resources/en/skills/monoco_core → features/agent/resources/en/skills/monoco_atom_core}/SKILL.md +2 -2
  51. monoco/features/agent/resources/en/skills/{flow_engineer → monoco_workflow_agent_engineer}/SKILL.md +2 -2
  52. monoco/features/agent/resources/en/skills/{flow_manager → monoco_workflow_agent_manager}/SKILL.md +2 -2
  53. monoco/features/agent/resources/en/skills/{flow_planner → monoco_workflow_agent_planner}/SKILL.md +2 -2
  54. monoco/features/agent/resources/en/skills/{flow_reviewer → monoco_workflow_agent_reviewer}/SKILL.md +2 -2
  55. monoco/features/agent/resources/{roles/role-engineer.yaml → zh/roles/monoco_role_engineer.yaml} +3 -3
  56. monoco/features/agent/resources/{roles/role-manager.yaml → zh/roles/monoco_role_manager.yaml} +8 -8
  57. monoco/features/agent/resources/{roles/role-planner.yaml → zh/roles/monoco_role_planner.yaml} +8 -8
  58. monoco/features/agent/resources/{roles/role-reviewer.yaml → zh/roles/monoco_role_reviewer.yaml} +8 -8
  59. monoco/{core/resources/zh/skills/monoco_core → features/agent/resources/zh/skills/monoco_atom_core}/SKILL.md +2 -2
  60. monoco/features/agent/resources/zh/skills/{flow_engineer → monoco_workflow_agent_engineer}/SKILL.md +2 -2
  61. monoco/features/agent/resources/zh/skills/{flow_manager → monoco_workflow_agent_manager}/SKILL.md +2 -2
  62. monoco/features/agent/resources/zh/skills/{flow_planner → monoco_workflow_agent_planner}/SKILL.md +2 -2
  63. monoco/features/agent/resources/zh/skills/{flow_reviewer → monoco_workflow_agent_reviewer}/SKILL.md +2 -2
  64. monoco/features/agent/worker.py +1 -1
  65. monoco/features/artifact/__init__.py +0 -0
  66. monoco/features/artifact/adapter.py +33 -0
  67. monoco/features/artifact/resources/zh/AGENTS.md +14 -0
  68. monoco/features/artifact/resources/zh/skills/monoco_atom_artifact/SKILL.md +278 -0
  69. monoco/features/glossary/adapter.py +18 -7
  70. monoco/features/glossary/resources/en/skills/{monoco_glossary → monoco_atom_glossary}/SKILL.md +2 -2
  71. monoco/features/glossary/resources/zh/skills/{monoco_glossary → monoco_atom_glossary}/SKILL.md +2 -2
  72. monoco/features/hooks/__init__.py +11 -0
  73. monoco/features/hooks/adapter.py +67 -0
  74. monoco/features/hooks/commands.py +309 -0
  75. monoco/features/hooks/core.py +441 -0
  76. monoco/features/hooks/resources/ADDING_HOOKS.md +234 -0
  77. monoco/features/i18n/adapter.py +18 -5
  78. monoco/features/i18n/core.py +482 -17
  79. monoco/features/i18n/resources/en/skills/{monoco_i18n → monoco_atom_i18n}/SKILL.md +2 -2
  80. monoco/features/i18n/resources/en/skills/{i18n_scan_workflow → monoco_workflow_i18n_scan}/SKILL.md +2 -2
  81. monoco/features/i18n/resources/zh/skills/{monoco_i18n → monoco_atom_i18n}/SKILL.md +2 -2
  82. monoco/features/i18n/resources/zh/skills/{i18n_scan_workflow → monoco_workflow_i18n_scan}/SKILL.md +2 -2
  83. monoco/features/issue/adapter.py +19 -6
  84. monoco/features/issue/commands.py +352 -20
  85. monoco/features/issue/core.py +475 -16
  86. monoco/features/issue/engine/machine.py +114 -4
  87. monoco/features/issue/linter.py +60 -5
  88. monoco/features/issue/models.py +2 -2
  89. monoco/features/issue/resources/en/AGENTS.md +109 -0
  90. monoco/features/issue/resources/en/skills/{monoco_issue → monoco_atom_issue}/SKILL.md +2 -2
  91. monoco/features/issue/resources/en/skills/{issue_create_workflow → monoco_workflow_issue_creation}/SKILL.md +2 -2
  92. monoco/features/issue/resources/en/skills/{issue_develop_workflow → monoco_workflow_issue_development}/SKILL.md +2 -2
  93. monoco/features/issue/resources/en/skills/{issue_lifecycle_workflow → monoco_workflow_issue_management}/SKILL.md +2 -2
  94. monoco/features/issue/resources/en/skills/{issue_refine_workflow → monoco_workflow_issue_refinement}/SKILL.md +2 -2
  95. monoco/features/issue/resources/hooks/post-checkout.sh +39 -0
  96. monoco/features/issue/resources/hooks/pre-commit.sh +41 -0
  97. monoco/features/issue/resources/hooks/pre-push.sh +35 -0
  98. monoco/features/issue/resources/zh/AGENTS.md +109 -0
  99. monoco/features/issue/resources/zh/skills/{monoco_issue → monoco_atom_issue_lifecycle}/SKILL.md +2 -2
  100. monoco/features/issue/resources/zh/skills/{issue_create_workflow → monoco_workflow_issue_creation}/SKILL.md +2 -2
  101. monoco/features/issue/resources/zh/skills/{issue_develop_workflow → monoco_workflow_issue_development}/SKILL.md +2 -2
  102. monoco/features/issue/resources/zh/skills/{issue_lifecycle_workflow → monoco_workflow_issue_management}/SKILL.md +2 -2
  103. monoco/features/issue/resources/zh/skills/{issue_refine_workflow → monoco_workflow_issue_refinement}/SKILL.md +2 -2
  104. monoco/features/issue/validator.py +101 -1
  105. monoco/features/memo/adapter.py +21 -8
  106. monoco/features/memo/cli.py +103 -10
  107. monoco/features/memo/core.py +178 -92
  108. monoco/features/memo/models.py +53 -0
  109. monoco/features/memo/resources/en/skills/{monoco_memo → monoco_atom_memo}/SKILL.md +2 -2
  110. monoco/features/memo/resources/en/skills/{note_processing_workflow → monoco_workflow_note_processing}/SKILL.md +2 -2
  111. monoco/features/memo/resources/zh/skills/{monoco_memo → monoco_atom_memo}/SKILL.md +2 -2
  112. monoco/features/memo/resources/zh/skills/{note_processing_workflow → monoco_workflow_note_processing}/SKILL.md +2 -2
  113. monoco/features/spike/adapter.py +18 -5
  114. monoco/features/spike/commands.py +5 -3
  115. monoco/features/spike/resources/en/skills/{monoco_spike → monoco_atom_spike}/SKILL.md +2 -2
  116. monoco/features/spike/resources/en/skills/{research_workflow → monoco_workflow_research}/SKILL.md +2 -2
  117. monoco/features/spike/resources/zh/skills/{monoco_spike → monoco_atom_spike}/SKILL.md +2 -2
  118. monoco/features/spike/resources/zh/skills/{research_workflow → monoco_workflow_research}/SKILL.md +2 -2
  119. monoco/main.py +38 -1
  120. {monoco_toolkit-0.3.10.dist-info → monoco_toolkit-0.3.12.dist-info}/METADATA +7 -1
  121. monoco_toolkit-0.3.12.dist-info/RECORD +202 -0
  122. monoco/features/agent/apoptosis.py +0 -44
  123. monoco/features/agent/manager.py +0 -91
  124. monoco/features/agent/session.py +0 -121
  125. monoco_toolkit-0.3.10.dist-info/RECORD +0 -156
  126. /monoco/{core → features/agent}/resources/en/AGENTS.md +0 -0
  127. /monoco/{core → features/agent}/resources/zh/AGENTS.md +0 -0
  128. {monoco_toolkit-0.3.10.dist-info → monoco_toolkit-0.3.12.dist-info}/WHEEL +0 -0
  129. {monoco_toolkit-0.3.10.dist-info → monoco_toolkit-0.3.12.dist-info}/entry_points.txt +0 -0
  130. {monoco_toolkit-0.3.10.dist-info → monoco_toolkit-0.3.12.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,296 @@
1
+ """
2
+ Field Watcher - YAML Front Matter field change detection.
3
+
4
+ Part of the Event Automation Framework.
5
+ Provides field-level change detection for Markdown files with YAML Front Matter.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ import re
12
+ from dataclasses import dataclass, field
13
+ from pathlib import Path
14
+ from typing import Any, Callable, Dict, List, Optional, Union
15
+
16
+ import yaml
17
+
18
+ from monoco.core.watcher.base import FieldChange, ChangeType
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ @dataclass
24
+ class FieldCondition:
25
+ """
26
+ Condition for field value matching.
27
+
28
+ Attributes:
29
+ field: Field name to check
30
+ operator: Comparison operator (eq, ne, gt, lt, gte, lte, in, contains)
31
+ value: Expected value
32
+ """
33
+ field: str
34
+ operator: str # eq, ne, gt, lt, gte, lte, in, contains
35
+ value: Any
36
+
37
+ OPERATORS = {
38
+ "eq": lambda a, b: a == b,
39
+ "ne": lambda a, b: a != b,
40
+ "gt": lambda a, b: a is not None and b is not None and a > b,
41
+ "lt": lambda a, b: a is not None and b is not None and a < b,
42
+ "gte": lambda a, b: a is not None and b is not None and a >= b,
43
+ "lte": lambda a, b: a is not None and b is not None and a <= b,
44
+ "in": lambda a, b: a in b if b is not None else False,
45
+ "contains": lambda a, b: b in a if a is not None else False,
46
+ "exists": lambda a, b: a is not None,
47
+ }
48
+
49
+ def evaluate(self, data: Dict[str, Any]) -> bool:
50
+ """Evaluate condition against data."""
51
+ actual_value = data.get(self.field)
52
+
53
+ op_func = self.OPERATORS.get(self.operator)
54
+ if not op_func:
55
+ logger.warning(f"Unknown operator: {self.operator}")
56
+ return False
57
+
58
+ try:
59
+ return op_func(actual_value, self.value)
60
+ except Exception as e:
61
+ logger.debug(f"Condition evaluation failed: {e}")
62
+ return False
63
+
64
+
65
+ class YAMLFrontMatterExtractor:
66
+ """
67
+ Extracts YAML Front Matter from Markdown files.
68
+
69
+ Provides methods to:
70
+ - Parse YAML Front Matter from content
71
+ - Extract specific fields
72
+ - Detect field changes between versions
73
+ """
74
+
75
+ FRONTMATTER_PATTERN = re.compile(
76
+ r'^---\s*\n(.*?)\n---\s*\n',
77
+ re.MULTILINE | re.DOTALL,
78
+ )
79
+
80
+ @classmethod
81
+ def extract(cls, content: str) -> Optional[Dict[str, Any]]:
82
+ """
83
+ Extract YAML Front Matter from markdown content.
84
+
85
+ Args:
86
+ content: Markdown file content
87
+
88
+ Returns:
89
+ Dict of front matter fields, or None if not found
90
+ """
91
+ match = cls.FRONTMATTER_PATTERN.match(content)
92
+ if not match:
93
+ return None
94
+
95
+ yaml_content = match.group(1)
96
+
97
+ try:
98
+ return yaml.safe_load(yaml_content) or {}
99
+ except yaml.YAMLError as e:
100
+ logger.warning(f"Failed to parse YAML front matter: {e}")
101
+ return None
102
+
103
+ @classmethod
104
+ def extract_from_file(cls, file_path: Path) -> Optional[Dict[str, Any]]:
105
+ """Extract YAML Front Matter from a file."""
106
+ try:
107
+ content = file_path.read_text(encoding="utf-8")
108
+ return cls.extract(content)
109
+ except Exception as e:
110
+ logger.debug(f"Could not read {file_path}: {e}")
111
+ return None
112
+
113
+ @classmethod
114
+ def get_field(cls, content: str, field_name: str) -> Any:
115
+ """Get a specific field from front matter."""
116
+ frontmatter = cls.extract(content)
117
+ if frontmatter is None:
118
+ return None
119
+ return frontmatter.get(field_name)
120
+
121
+ @classmethod
122
+ def detect_changes(
123
+ cls,
124
+ old_content: str,
125
+ new_content: str,
126
+ tracked_fields: Optional[List[str]] = None,
127
+ ) -> List[FieldChange]:
128
+ """
129
+ Detect changes in front matter fields.
130
+
131
+ Args:
132
+ old_content: Previous file content
133
+ new_content: Current file content
134
+ tracked_fields: List of fields to track (None = all)
135
+
136
+ Returns:
137
+ List of FieldChange objects
138
+ """
139
+ old_fm = cls.extract(old_content) or {}
140
+ new_fm = cls.extract(new_content) or {}
141
+
142
+ changes = []
143
+
144
+ # Determine which fields to check
145
+ if tracked_fields:
146
+ fields_to_check = tracked_fields
147
+ else:
148
+ fields_to_check = list(set(old_fm.keys()) | set(new_fm.keys()))
149
+
150
+ for field_name in fields_to_check:
151
+ old_value = old_fm.get(field_name)
152
+ new_value = new_fm.get(field_name)
153
+
154
+ if old_value != new_value:
155
+ # Determine change type
156
+ if old_value is None and new_value is not None:
157
+ change_type = ChangeType.CREATED
158
+ elif old_value is not None and new_value is None:
159
+ change_type = ChangeType.DELETED
160
+ else:
161
+ change_type = ChangeType.MODIFIED
162
+
163
+ changes.append(FieldChange(
164
+ field_name=field_name,
165
+ old_value=old_value,
166
+ new_value=new_value,
167
+ change_type=change_type,
168
+ ))
169
+
170
+ return changes
171
+
172
+
173
+ class FieldWatcher:
174
+ """
175
+ Watches specific fields for changes and triggers conditions.
176
+
177
+ Maintains a cache of field values and emits events when:
178
+ - Fields change
179
+ - Conditions are met
180
+
181
+ Example:
182
+ >>> watcher = FieldWatcher(
183
+ ... tracked_fields=["status", "stage"],
184
+ ... )
185
+ >>>
186
+ >>> # Add a condition
187
+ >>> watcher.add_condition(FieldCondition(
188
+ ... field="stage",
189
+ ... operator="eq",
190
+ ... value="doing",
191
+ ... ))
192
+ >>>
193
+ >>> # Check file
194
+ >>> changes = watcher.check_file(path, content)
195
+ """
196
+
197
+ def __init__(
198
+ self,
199
+ tracked_fields: Optional[List[str]] = None,
200
+ ):
201
+ self.tracked_fields = tracked_fields
202
+ self._field_cache: Dict[str, Dict[str, Any]] = {} # file_path -> field_values
203
+ self._conditions: List[FieldCondition] = []
204
+ self._condition_callbacks: List[Callable[[str, FieldCondition, Dict[str, Any]], None]] = []
205
+
206
+ def add_condition(self, condition: FieldCondition) -> None:
207
+ """Add a condition to watch for."""
208
+ self._conditions.append(condition)
209
+
210
+ def add_callback(
211
+ self,
212
+ callback: Callable[[str, FieldCondition, Dict[str, Any]], None],
213
+ ) -> None:
214
+ """Add a callback for when conditions are met."""
215
+ self._condition_callbacks.append(callback)
216
+
217
+ def check_file(
218
+ self,
219
+ file_path: Union[str, Path],
220
+ content: str,
221
+ ) -> List[FieldChange]:
222
+ """
223
+ Check a file for field changes.
224
+
225
+ Args:
226
+ file_path: Path to the file
227
+ content: Current file content
228
+
229
+ Returns:
230
+ List of field changes
231
+ """
232
+ path_key = str(file_path)
233
+
234
+ # Extract current fields
235
+ current_fm = YAMLFrontMatterExtractor.extract(content) or {}
236
+
237
+ if self.tracked_fields:
238
+ current_fields = {
239
+ f: current_fm.get(f)
240
+ for f in self.tracked_fields
241
+ }
242
+ else:
243
+ current_fields = current_fm
244
+
245
+ # Get cached fields
246
+ cached_fields = self._field_cache.get(path_key, {})
247
+
248
+ # Detect changes
249
+ changes = []
250
+ for field_name, new_value in current_fields.items():
251
+ old_value = cached_fields.get(field_name)
252
+ if old_value != new_value:
253
+ changes.append(FieldChange(
254
+ field_name=field_name,
255
+ old_value=old_value,
256
+ new_value=new_value,
257
+ change_type=ChangeType.MODIFIED if old_value is not None else ChangeType.CREATED,
258
+ ))
259
+
260
+ # Update cache
261
+ self._field_cache[path_key] = current_fields
262
+
263
+ # Check conditions
264
+ if changes:
265
+ self._check_conditions(path_key, current_fields)
266
+
267
+ return changes
268
+
269
+ def _check_conditions(self, file_path: str, fields: Dict[str, Any]) -> None:
270
+ """Check if any conditions are met."""
271
+ for condition in self._conditions:
272
+ if condition.evaluate(fields):
273
+ for callback in self._condition_callbacks:
274
+ try:
275
+ callback(file_path, condition, fields)
276
+ except Exception as e:
277
+ logger.error(f"Condition callback error: {e}")
278
+
279
+ def get_cached_fields(self, file_path: Union[str, Path]) -> Optional[Dict[str, Any]]:
280
+ """Get cached fields for a file."""
281
+ return self._field_cache.get(str(file_path))
282
+
283
+ def clear_cache(self, file_path: Optional[Union[str, Path]] = None) -> None:
284
+ """Clear the field cache."""
285
+ if file_path:
286
+ self._field_cache.pop(str(file_path), None)
287
+ else:
288
+ self._field_cache.clear()
289
+
290
+ def get_stats(self) -> Dict[str, Any]:
291
+ """Get watcher statistics."""
292
+ return {
293
+ "tracked_files": len(self._field_cache),
294
+ "tracked_fields": self.tracked_fields,
295
+ "conditions": len(self._conditions),
296
+ }