monoco-toolkit 0.3.5__py3-none-any.whl → 0.3.9__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 (59) hide show
  1. monoco/cli/workspace.py +1 -1
  2. monoco/core/config.py +51 -0
  3. monoco/core/hooks/__init__.py +19 -0
  4. monoco/core/hooks/base.py +104 -0
  5. monoco/core/hooks/builtin/__init__.py +11 -0
  6. monoco/core/hooks/builtin/git_cleanup.py +266 -0
  7. monoco/core/hooks/builtin/logging_hook.py +78 -0
  8. monoco/core/hooks/context.py +131 -0
  9. monoco/core/hooks/registry.py +222 -0
  10. monoco/core/integrations.py +6 -0
  11. monoco/core/registry.py +2 -0
  12. monoco/core/setup.py +1 -1
  13. monoco/core/skills.py +226 -42
  14. monoco/features/{scheduler → agent}/__init__.py +4 -2
  15. monoco/features/{scheduler → agent}/cli.py +134 -80
  16. monoco/features/{scheduler → agent}/config.py +17 -3
  17. monoco/features/agent/defaults.py +55 -0
  18. monoco/features/agent/flow_skills.py +281 -0
  19. monoco/features/{scheduler → agent}/manager.py +39 -2
  20. monoco/features/{scheduler → agent}/models.py +6 -3
  21. monoco/features/{scheduler → agent}/reliability.py +1 -1
  22. monoco/features/agent/resources/skills/flow_engineer/SKILL.md +94 -0
  23. monoco/features/agent/resources/skills/flow_manager/SKILL.md +88 -0
  24. monoco/features/agent/resources/skills/flow_reviewer/SKILL.md +114 -0
  25. monoco/features/{scheduler → agent}/session.py +39 -5
  26. monoco/features/{scheduler → agent}/worker.py +2 -2
  27. monoco/features/i18n/resources/skills/i18n_scan_workflow/SKILL.md +105 -0
  28. monoco/features/issue/commands.py +427 -21
  29. monoco/features/issue/core.py +104 -0
  30. monoco/features/issue/criticality.py +553 -0
  31. monoco/features/issue/domain/models.py +28 -2
  32. monoco/features/issue/engine/machine.py +65 -37
  33. monoco/features/issue/git_service.py +185 -0
  34. monoco/features/issue/linter.py +291 -62
  35. monoco/features/issue/models.py +91 -14
  36. monoco/features/issue/resources/en/SKILL.md +48 -0
  37. monoco/features/issue/resources/skills/issue_lifecycle_workflow/SKILL.md +159 -0
  38. monoco/features/issue/resources/zh/SKILL.md +50 -0
  39. monoco/features/issue/test_priority_integration.py +1 -0
  40. monoco/features/issue/validator.py +185 -65
  41. monoco/features/memo/__init__.py +4 -0
  42. monoco/features/memo/adapter.py +32 -0
  43. monoco/features/memo/cli.py +112 -0
  44. monoco/features/memo/core.py +146 -0
  45. monoco/features/memo/resources/skills/note_processing_workflow/SKILL.md +140 -0
  46. monoco/features/memo/resources/zh/AGENTS.md +8 -0
  47. monoco/features/memo/resources/zh/SKILL.md +75 -0
  48. monoco/features/spike/resources/skills/research_workflow/SKILL.md +121 -0
  49. monoco/main.py +6 -3
  50. {monoco_toolkit-0.3.5.dist-info → monoco_toolkit-0.3.9.dist-info}/METADATA +1 -1
  51. {monoco_toolkit-0.3.5.dist-info → monoco_toolkit-0.3.9.dist-info}/RECORD +56 -35
  52. monoco/features/scheduler/defaults.py +0 -54
  53. monoco/features/skills/__init__.py +0 -0
  54. monoco/features/skills/core.py +0 -102
  55. /monoco/core/{hooks.py → githooks.py} +0 -0
  56. /monoco/features/{scheduler → agent}/engines.py +0 -0
  57. {monoco_toolkit-0.3.5.dist-info → monoco_toolkit-0.3.9.dist-info}/WHEEL +0 -0
  58. {monoco_toolkit-0.3.5.dist-info → monoco_toolkit-0.3.9.dist-info}/entry_points.txt +0 -0
  59. {monoco_toolkit-0.3.5.dist-info → monoco_toolkit-0.3.9.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,11 @@
1
1
  from typing import List, Optional, Dict
2
2
  from monoco.core.config import IssueSchemaConfig, TransitionConfig
3
3
  from ..models import IssueMetadata
4
+ from ..criticality import (
5
+ CriticalityLevel,
6
+ PolicyResolver,
7
+ HumanReviewLevel,
8
+ )
4
9
 
5
10
 
6
11
  class StateMachine:
@@ -128,9 +133,11 @@ class StateMachine:
128
133
  to_status: str,
129
134
  to_stage: Optional[str],
130
135
  solution: Optional[str] = None,
136
+ meta: Optional[IssueMetadata] = None,
131
137
  ) -> None:
132
138
  """
133
139
  Validate if a transition is allowed. Raises ValueError if not.
140
+ If meta is provided, also validates criticality-based policies.
134
141
  """
135
142
  if from_status == to_status and from_stage == to_stage:
136
143
  return # No change is always allowed (unless we want to enforce specific updates)
@@ -150,57 +157,72 @@ class StateMachine:
150
157
  f"Lifecycle Policy: Transition '{transition.label}' requires solution '{transition.required_solution}'."
151
158
  )
152
159
 
153
- def enforce_policy(self, meta: IssueMetadata) -> None:
154
- """
155
- Apply consistency rules to IssueMetadata.
156
- """
157
- from ..models import current_time
158
-
159
- if meta.status == "backlog":
160
- meta.stage = "freezed"
161
-
162
- elif meta.status == "closed":
163
- if meta.stage != "done":
164
- meta.stage = "done"
165
- if not meta.closed_at:
166
- meta.closed_at = current_time()
160
+ # Criticality-based policy checks
161
+ if meta and meta.criticality:
162
+ self._validate_criticality_policy(meta, from_stage, to_stage)
167
163
 
168
- elif meta.status == "open":
169
- if meta.stage is None:
170
- meta.stage = "draft"
171
-
172
- def validate_transition(
164
+ def _validate_criticality_policy(
173
165
  self,
174
- from_status: str,
166
+ meta: IssueMetadata,
175
167
  from_stage: Optional[str],
176
- to_status: str,
177
168
  to_stage: Optional[str],
178
- solution: Optional[str] = None,
179
169
  ) -> None:
180
170
  """
181
- Validate if a transition is allowed. Raises ValueError if not.
171
+ Validate transition against criticality-based policies.
172
+ Enforces stricter requirements for high/critical issues.
182
173
  """
183
- if from_status == to_status and from_stage == to_stage:
184
- return # No change is always allowed (unless we want to enforce specific updates)
174
+ policy = PolicyResolver.resolve(meta.criticality)
175
+
176
+ # Submit to Review: Enforce agent review for medium+
177
+ if to_stage == "review" and from_stage == "doing":
178
+ if meta.criticality >= CriticalityLevel.MEDIUM:
179
+ # For medium+, agent review is mandatory
180
+ # This is enforced by the policy, but we can't check actual review status here
181
+ # The check is informational - actual enforcement happens in submit command
182
+ pass
183
+
184
+ # Close/Accept: Enforce human review for high/critical
185
+ if to_stage == "done":
186
+ if policy.human_review in [
187
+ HumanReviewLevel.REQUIRED,
188
+ HumanReviewLevel.REQUIRED_RECORD,
189
+ ]:
190
+ # For high/critical, human review is mandatory before closing
191
+ # Actual enforcement would check review comments section
192
+ pass
193
+
194
+ def check_policy_compliance(self, meta: IssueMetadata) -> List[str]:
195
+ """
196
+ Check if an issue complies with its criticality policy.
197
+ Returns list of policy violations.
198
+ """
199
+ if not meta.criticality:
200
+ return []
185
201
 
186
- transition = self.find_transition(
187
- from_status, from_stage, to_status, to_stage, solution
188
- )
202
+ violations = []
203
+ policy = PolicyResolver.resolve(meta.criticality)
189
204
 
190
- if not transition:
191
- raise ValueError(
192
- f"Lifecycle Policy: Transition from {from_status}({from_stage if from_stage else 'None'}) "
193
- f"to {to_status}({to_stage if to_stage else 'None'}) is not defined."
194
- )
205
+ # Stage-based checks
206
+ if meta.stage == "review":
207
+ # In review stage, check coverage requirement
208
+ # Note: Actual coverage check would require external data
209
+ pass
195
210
 
196
- if transition.required_solution and solution != transition.required_solution:
197
- raise ValueError(
198
- f"Lifecycle Policy: Transition '{transition.label}' requires solution '{transition.required_solution}'."
199
- )
211
+ if meta.stage == "done" or meta.status == "closed":
212
+ # For high/critical, require review comments
213
+ if policy.human_review in [
214
+ HumanReviewLevel.REQUIRED,
215
+ HumanReviewLevel.REQUIRED_RECORD,
216
+ ]:
217
+ # This is a simplified check - full implementation would parse body
218
+ pass
219
+
220
+ return violations
200
221
 
201
222
  def enforce_policy(self, meta: IssueMetadata) -> None:
202
223
  """
203
224
  Apply consistency rules to IssueMetadata.
225
+ Includes criticality-based defaults.
204
226
  """
205
227
  from ..models import current_time
206
228
 
@@ -216,3 +238,9 @@ class StateMachine:
216
238
  elif meta.status == "open":
217
239
  if meta.stage is None:
218
240
  meta.stage = "draft"
241
+
242
+ # Set default criticality if not set
243
+ if meta.criticality is None:
244
+ from ..criticality import CriticalityTypeMapping
245
+
246
+ meta.criticality = CriticalityTypeMapping.get_default(meta.type.value)
@@ -0,0 +1,185 @@
1
+ """
2
+ Git service for Issue operations.
3
+
4
+ Provides atomic commit functionality for issue file changes,
5
+ ensuring task state transitions are properly tracked in git history.
6
+ """
7
+
8
+ import logging
9
+ from pathlib import Path
10
+ from typing import Optional, List, Tuple
11
+ from dataclasses import dataclass
12
+
13
+ from monoco.core import git
14
+
15
+ logger = logging.getLogger("monoco.features.issue.git_service")
16
+
17
+
18
+ @dataclass
19
+ class CommitResult:
20
+ """Result of a commit operation."""
21
+
22
+ success: bool
23
+ commit_hash: Optional[str] = None
24
+ message: Optional[str] = None
25
+ error: Optional[str] = None
26
+
27
+
28
+ class IssueGitService:
29
+ """
30
+ Service for handling git operations related to Issue files.
31
+
32
+ Responsibilities:
33
+ - Detect if current directory is in a git repository
34
+ - Stage specific issue files
35
+ - Generate atomic commit messages for issue transitions
36
+ - Handle graceful degradation when not in a git repo
37
+ """
38
+
39
+ def __init__(self, project_root: Path):
40
+ self.project_root = project_root
41
+ self._is_git_repo: Optional[bool] = None
42
+
43
+ def is_git_repository(self) -> bool:
44
+ """Check if the project root is inside a git repository."""
45
+ if self._is_git_repo is None:
46
+ self._is_git_repo = git.is_git_repo(self.project_root)
47
+ return self._is_git_repo
48
+
49
+ def commit_issue_change(
50
+ self,
51
+ issue_id: str,
52
+ action: str,
53
+ issue_file_path: Path,
54
+ old_file_path: Optional[Path] = None,
55
+ no_commit: bool = False,
56
+ ) -> CommitResult:
57
+ """
58
+ Atomically commit an issue file change.
59
+
60
+ Args:
61
+ issue_id: The issue ID (e.g., "FEAT-0115")
62
+ action: The action being performed (e.g., "close", "start", "open")
63
+ issue_file_path: Current path to the issue file
64
+ old_file_path: Previous path if the file was moved (e.g., status change)
65
+ no_commit: If True, skip the commit operation
66
+
67
+ Returns:
68
+ CommitResult with success status and commit details
69
+ """
70
+ if no_commit:
71
+ return CommitResult(success=True, message="Skipped (no-commit flag)")
72
+
73
+ if not self.is_git_repository():
74
+ logger.info("Not in a git repository, skipping auto-commit")
75
+ return CommitResult(success=True, message="Skipped (not a git repo)")
76
+
77
+ try:
78
+ # Stage the changes
79
+ self._stage_issue_files(issue_file_path, old_file_path)
80
+
81
+ # Generate and execute commit
82
+ commit_message = self._generate_commit_message(issue_id, action)
83
+ commit_hash = git.git_commit(self.project_root, commit_message)
84
+
85
+ return CommitResult(
86
+ success=True,
87
+ commit_hash=commit_hash,
88
+ message=commit_message,
89
+ )
90
+
91
+ except Exception as e:
92
+ error_msg = str(e)
93
+ logger.error(f"Failed to commit issue change: {error_msg}")
94
+ return CommitResult(success=False, error=error_msg)
95
+
96
+ def _stage_issue_files(
97
+ self, current_path: Path, old_path: Optional[Path] = None
98
+ ) -> None:
99
+ """
100
+ Stage issue file changes.
101
+
102
+ If old_path is provided (file was moved), handles the rename properly:
103
+ - Stages deletion of old file
104
+ - Stages addition of new file
105
+ """
106
+ files_to_stage: List[str] = []
107
+
108
+ # Handle the old file path if file was moved (e.g., open -> closed)
109
+ if old_path and old_path.exists():
110
+ # File was moved, need to stage the deletion
111
+ try:
112
+ rel_old_path = old_path.relative_to(self.project_root)
113
+ files_to_stage.append(str(rel_old_path))
114
+ except ValueError:
115
+ # old_path is not relative to project_root, use absolute
116
+ files_to_stage.append(str(old_path))
117
+
118
+ # Handle the current file path
119
+ if current_path.exists():
120
+ try:
121
+ rel_path = current_path.relative_to(self.project_root)
122
+ files_to_stage.append(str(rel_path))
123
+ except ValueError:
124
+ # current_path is not relative to project_root, use absolute
125
+ files_to_stage.append(str(current_path))
126
+
127
+ if files_to_stage:
128
+ git.git_add(self.project_root, files_to_stage)
129
+
130
+ def _generate_commit_message(self, issue_id: str, action: str) -> str:
131
+ """
132
+ Generate a standardized commit message for issue transitions.
133
+
134
+ Format: chore(issue): <action> <issue_id>
135
+
136
+ Examples:
137
+ chore(issue): close FIX-0020
138
+ chore(issue): start FEAT-0115
139
+ chore(issue): open FEAT-0115
140
+ chore(issue): submit FEAT-0115
141
+ """
142
+ return f"chore(issue): {action} {issue_id}"
143
+
144
+ def get_commit_history(
145
+ self, issue_id: str, max_count: int = 10
146
+ ) -> List[Tuple[str, str]]:
147
+ """
148
+ Get commit history for a specific issue.
149
+
150
+ Returns:
151
+ List of (commit_hash, subject) tuples
152
+ """
153
+ if not self.is_git_repository():
154
+ return []
155
+
156
+ try:
157
+ commits = git.search_commits_by_message(self.project_root, issue_id)
158
+ return [(c["hash"], c["subject"]) for c in commits[:max_count]]
159
+ except Exception:
160
+ return []
161
+
162
+
163
+ def should_auto_commit(config) -> bool:
164
+ """
165
+ Check if auto-commit should be enabled based on configuration.
166
+
167
+ Checks for:
168
+ 1. Explicit disable in config: issue.auto_commit = false
169
+ 2. Environment variable: MONOCO_NO_AUTO_COMMIT=1
170
+ """
171
+ import os
172
+
173
+ # Check environment variable first
174
+ if os.environ.get("MONOCO_NO_AUTO_COMMIT", "0") == "1":
175
+ return False
176
+
177
+ # Check config (if issue config has auto_commit setting)
178
+ try:
179
+ if hasattr(config, "issue") and hasattr(config.issue, "auto_commit"):
180
+ return config.issue.auto_commit
181
+ except Exception:
182
+ pass
183
+
184
+ # Default to enabled
185
+ return True