monoco-toolkit 0.1.1__py3-none-any.whl → 0.2.8__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 (76) hide show
  1. monoco/cli/__init__.py +0 -0
  2. monoco/cli/project.py +87 -0
  3. monoco/cli/workspace.py +46 -0
  4. monoco/core/agent/__init__.py +5 -0
  5. monoco/core/agent/action.py +144 -0
  6. monoco/core/agent/adapters.py +129 -0
  7. monoco/core/agent/protocol.py +31 -0
  8. monoco/core/agent/state.py +106 -0
  9. monoco/core/config.py +212 -17
  10. monoco/core/execution.py +62 -0
  11. monoco/core/feature.py +58 -0
  12. monoco/core/git.py +51 -2
  13. monoco/core/injection.py +196 -0
  14. monoco/core/integrations.py +242 -0
  15. monoco/core/lsp.py +68 -0
  16. monoco/core/output.py +21 -3
  17. monoco/core/registry.py +36 -0
  18. monoco/core/resources/en/AGENTS.md +8 -0
  19. monoco/core/resources/en/SKILL.md +66 -0
  20. monoco/core/resources/zh/AGENTS.md +8 -0
  21. monoco/core/resources/zh/SKILL.md +65 -0
  22. monoco/core/setup.py +96 -110
  23. monoco/core/skills.py +444 -0
  24. monoco/core/state.py +53 -0
  25. monoco/core/sync.py +224 -0
  26. monoco/core/telemetry.py +4 -1
  27. monoco/core/workspace.py +85 -20
  28. monoco/daemon/app.py +127 -58
  29. monoco/daemon/models.py +4 -0
  30. monoco/daemon/services.py +56 -155
  31. monoco/features/config/commands.py +125 -44
  32. monoco/features/i18n/adapter.py +29 -0
  33. monoco/features/i18n/commands.py +89 -10
  34. monoco/features/i18n/core.py +113 -27
  35. monoco/features/i18n/resources/en/AGENTS.md +8 -0
  36. monoco/features/i18n/resources/en/SKILL.md +94 -0
  37. monoco/features/i18n/resources/zh/AGENTS.md +8 -0
  38. monoco/features/i18n/resources/zh/SKILL.md +94 -0
  39. monoco/features/issue/adapter.py +34 -0
  40. monoco/features/issue/commands.py +343 -101
  41. monoco/features/issue/core.py +384 -150
  42. monoco/features/issue/domain/__init__.py +0 -0
  43. monoco/features/issue/domain/lifecycle.py +126 -0
  44. monoco/features/issue/domain/models.py +170 -0
  45. monoco/features/issue/domain/parser.py +223 -0
  46. monoco/features/issue/domain/workspace.py +104 -0
  47. monoco/features/issue/engine/__init__.py +22 -0
  48. monoco/features/issue/engine/config.py +172 -0
  49. monoco/features/issue/engine/machine.py +185 -0
  50. monoco/features/issue/engine/models.py +18 -0
  51. monoco/features/issue/linter.py +325 -120
  52. monoco/features/issue/lsp/__init__.py +3 -0
  53. monoco/features/issue/lsp/definition.py +72 -0
  54. monoco/features/issue/migration.py +134 -0
  55. monoco/features/issue/models.py +46 -24
  56. monoco/features/issue/monitor.py +94 -0
  57. monoco/features/issue/resources/en/AGENTS.md +20 -0
  58. monoco/features/issue/resources/en/SKILL.md +111 -0
  59. monoco/features/issue/resources/zh/AGENTS.md +20 -0
  60. monoco/features/issue/resources/zh/SKILL.md +138 -0
  61. monoco/features/issue/validator.py +455 -0
  62. monoco/features/spike/adapter.py +30 -0
  63. monoco/features/spike/commands.py +45 -24
  64. monoco/features/spike/core.py +6 -40
  65. monoco/features/spike/resources/en/AGENTS.md +7 -0
  66. monoco/features/spike/resources/en/SKILL.md +74 -0
  67. monoco/features/spike/resources/zh/AGENTS.md +7 -0
  68. monoco/features/spike/resources/zh/SKILL.md +74 -0
  69. monoco/main.py +91 -2
  70. monoco_toolkit-0.2.8.dist-info/METADATA +136 -0
  71. monoco_toolkit-0.2.8.dist-info/RECORD +83 -0
  72. monoco_toolkit-0.1.1.dist-info/METADATA +0 -93
  73. monoco_toolkit-0.1.1.dist-info/RECORD +0 -33
  74. {monoco_toolkit-0.1.1.dist-info → monoco_toolkit-0.2.8.dist-info}/WHEEL +0 -0
  75. {monoco_toolkit-0.1.1.dist-info → monoco_toolkit-0.2.8.dist-info}/entry_points.txt +0 -0
  76. {monoco_toolkit-0.1.1.dist-info → monoco_toolkit-0.2.8.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,172 @@
1
+ from monoco.core.config import IssueSchemaConfig, IssueTypeConfig, TransitionConfig, StateMachineConfig
2
+
3
+ DEFAULT_ISSUE_CONFIG = IssueSchemaConfig(
4
+ types=[
5
+ IssueTypeConfig(name="epic", label="Epic", prefix="EPIC", folder="Epics"),
6
+ IssueTypeConfig(name="feature", label="Feature", prefix="FEAT", folder="Features"),
7
+ IssueTypeConfig(name="chore", label="Chore", prefix="CHORE", folder="Chores"),
8
+ IssueTypeConfig(name="fix", label="Fix", prefix="FIX", folder="Fixes"),
9
+ ],
10
+ statuses=["open", "closed", "backlog"],
11
+ stages=["draft", "doing", "review", "done", "freezed"],
12
+ solutions=["implemented", "cancelled", "wontfix", "duplicate"],
13
+ workflows=[
14
+ # --- UNIVERSAL AGENT ACTIONS ---
15
+
16
+
17
+ # --- OPEN -> OPEN Transitions (Stage changes) ---
18
+ TransitionConfig(
19
+ name="start",
20
+ label="Start",
21
+ icon="$(play)",
22
+ from_status="open",
23
+ from_stage="draft",
24
+ to_status="open",
25
+ to_stage="doing",
26
+ command_template="monoco issue start {id}",
27
+ description="Start working on the issue"
28
+ ),
29
+
30
+
31
+
32
+ TransitionConfig(
33
+ name="stop",
34
+ label="Stop",
35
+ icon="$(stop)",
36
+ from_status="open",
37
+ from_stage="doing",
38
+ to_status="open",
39
+ to_stage="draft",
40
+ command_template="monoco issue stop {id}",
41
+ description="Stop working and return to draft"
42
+ ),
43
+ TransitionConfig(
44
+ name="submit",
45
+ label="Submit",
46
+ icon="$(check)",
47
+ from_status="open",
48
+ from_stage="doing",
49
+ to_status="open",
50
+ to_stage="review",
51
+ command_template="monoco issue submit {id}",
52
+ description="Submit for review"
53
+ ),
54
+ TransitionConfig(
55
+ name="reject",
56
+ label="Reject",
57
+ icon="$(error)",
58
+ from_status="open",
59
+ from_stage="review",
60
+ to_status="open",
61
+ to_stage="doing",
62
+ command_template="monoco issue update {id} --stage doing",
63
+ description="Reject review and return to doing"
64
+ ),
65
+
66
+ # --- OPEN -> CLOSED Transitions ---
67
+ TransitionConfig(
68
+ name="accept",
69
+ label="Accept",
70
+ icon="$(pass-filled)",
71
+ from_status="open",
72
+ from_stage="review",
73
+ to_status="closed",
74
+ to_stage="done",
75
+ required_solution="implemented",
76
+ command_template="monoco issue close {id} --solution implemented",
77
+ description="Accept and close issue"
78
+ ),
79
+ TransitionConfig(
80
+ name="close_done",
81
+ label="Close",
82
+ icon="$(close)",
83
+ from_status="open",
84
+ from_stage="done",
85
+ to_status="closed",
86
+ to_stage="done",
87
+ required_solution="implemented",
88
+ command_template="monoco issue close {id} --solution implemented",
89
+ description="Close completed issue"
90
+ ),
91
+ TransitionConfig(
92
+ name="cancel",
93
+ label="Cancel",
94
+ icon="$(trash)",
95
+ from_status="open",
96
+ # Allowed from any stage except DONE (though core.py had a check for it)
97
+ to_status="closed",
98
+ to_stage="done",
99
+ required_solution="cancelled",
100
+ command_template="monoco issue cancel {id}",
101
+ description="Cancel the issue"
102
+ ),
103
+ TransitionConfig(
104
+ name="wontfix",
105
+ label="Won't Fix",
106
+ icon="$(circle-slash)",
107
+ from_status="open",
108
+ to_status="closed",
109
+ to_stage="done",
110
+ required_solution="wontfix",
111
+ command_template="monoco issue close {id} --solution wontfix",
112
+ description="Mark as won't fix"
113
+ ),
114
+
115
+ # --- BACKLOG Transitions ---
116
+ TransitionConfig(
117
+ name="push",
118
+ label="Push to Backlog",
119
+ icon="$(archive)",
120
+ from_status="open",
121
+ to_status="backlog",
122
+ to_stage="freezed",
123
+ command_template="monoco issue backlog push {id}",
124
+ description="Move issue to backlog"
125
+ ),
126
+
127
+ TransitionConfig(
128
+ name="pull",
129
+ label="Pull",
130
+ icon="$(arrow-up)",
131
+ from_status="backlog",
132
+ to_status="open",
133
+ to_stage="draft",
134
+ command_template="monoco issue backlog pull {id}",
135
+ description="Restore issue from backlog"
136
+ ),
137
+ TransitionConfig(
138
+ name="cancel_backlog",
139
+ label="Cancel",
140
+ icon="$(trash)",
141
+ from_status="backlog",
142
+ to_status="closed",
143
+ to_stage="done",
144
+ required_solution="cancelled",
145
+ command_template="monoco issue cancel {id}",
146
+ description="Cancel backlog issue"
147
+ ),
148
+
149
+ # --- CLOSED Transitions ---
150
+ TransitionConfig(
151
+ name="reopen",
152
+ label="Reopen",
153
+ icon="$(refresh)",
154
+ from_status="closed",
155
+ to_status="open",
156
+ to_stage="draft",
157
+ command_template="monoco issue open {id}",
158
+ description="Reopen a closed issue"
159
+ ),
160
+ TransitionConfig(
161
+ name="reopen_from_done",
162
+ label="Reopen",
163
+ icon="$(refresh)",
164
+ from_status="open",
165
+ from_stage="done",
166
+ to_status="open",
167
+ to_stage="draft",
168
+ command_template="monoco issue open {id}",
169
+ description="Reopen a done issue"
170
+ ),
171
+ ]
172
+ )
@@ -0,0 +1,185 @@
1
+ from typing import List, Optional, Dict
2
+ from monoco.core.config import IssueSchemaConfig, TransitionConfig
3
+ from ..models import IssueStatus, IssueStage, IssueMetadata, IssueSolution, IssueType
4
+
5
+ class StateMachine:
6
+ def __init__(self, config: IssueSchemaConfig):
7
+ self.issue_config = config
8
+ self.transitions = config.workflows or []
9
+
10
+ def get_type_config(self, type_name: str):
11
+ if not self.issue_config.types:
12
+ return None
13
+ for t in self.issue_config.types:
14
+ if t.name == type_name:
15
+ return t
16
+ return None
17
+
18
+ def get_prefix_map(self) -> Dict[str, str]:
19
+ if not self.issue_config.types:
20
+ return {}
21
+ return {t.name: t.prefix for t in self.issue_config.types}
22
+
23
+ def get_folder_map(self) -> Dict[str, str]:
24
+ if not self.issue_config.types:
25
+ return {}
26
+ return {t.name: t.folder for t in self.issue_config.types}
27
+
28
+ def get_all_types(self) -> List[str]:
29
+ if not self.issue_config.types:
30
+ return []
31
+ return [t.name for t in self.issue_config.types]
32
+
33
+ def can_transition(self, current_status: str, current_stage: Optional[str],
34
+ target_status: str, target_stage: Optional[str]) -> bool:
35
+ """Check if a transition path exists."""
36
+ for t in self.transitions:
37
+ if t.from_status and t.from_status != current_status:
38
+ continue
39
+ if t.from_stage and t.from_stage != current_stage:
40
+ continue
41
+
42
+ if t.to_status == target_status:
43
+ if target_stage is None or t.to_stage == target_stage:
44
+ return True
45
+ return False
46
+
47
+ def get_available_transitions(self, meta: IssueMetadata) -> List[TransitionConfig]:
48
+ """Get all transitions allowed from the current state of the issue."""
49
+ allowed = []
50
+ for t in self.transitions:
51
+ # Universal actions (no from_status/stage) are always allowed
52
+ if t.from_status is None and t.from_stage is None:
53
+ allowed.append(t)
54
+ continue
55
+
56
+ # Match status
57
+ if t.from_status and t.from_status != meta.status:
58
+ continue
59
+
60
+ # Match stage
61
+ if t.from_stage and t.from_stage != meta.stage:
62
+ continue
63
+
64
+ # Special case for 'Cancel': don't show if already DONE or CLOSED
65
+ if t.name == "cancel" and meta.stage == "done":
66
+ continue
67
+
68
+ allowed.append(t)
69
+ return allowed
70
+
71
+ def find_transition(self, from_status: str, from_stage: Optional[str],
72
+ to_status: str, to_stage: Optional[str],
73
+ solution: Optional[str] = None) -> Optional[TransitionConfig]:
74
+ """Find a specific transition rule."""
75
+ candidates = []
76
+ for t in self.transitions:
77
+ # Skip non-transitions (agent actions with same status/stage)
78
+ if t.from_status is None and t.from_stage is None:
79
+ continue
80
+
81
+ if t.from_status and t.from_status != from_status:
82
+ continue
83
+ if t.from_stage and t.from_stage != from_stage:
84
+ continue
85
+
86
+ # Check if this transition matches the target
87
+ if t.to_status == to_status:
88
+ if to_stage is None or t.to_stage == to_stage:
89
+ candidates.append(t)
90
+
91
+ if not candidates:
92
+ return None
93
+
94
+ # If we have a solution, find the transition that requires it
95
+ if solution:
96
+ for t in candidates:
97
+ if t.required_solution == solution:
98
+ return t
99
+ # If solution provided but none of the transitions match it,
100
+ # we should return None (unless there is a transition with NO required_solution)
101
+ for t in candidates:
102
+ if t.required_solution is None:
103
+ return t
104
+ return None
105
+
106
+ # Otherwise return the first one that has NO required_solution
107
+ for t in candidates:
108
+ if t.required_solution is None:
109
+ return t
110
+
111
+ return candidates[0]
112
+
113
+ def validate_transition(self, from_status: str, from_stage: Optional[str],
114
+ to_status: str, to_stage: Optional[str],
115
+ solution: Optional[str] = None) -> None:
116
+ """
117
+ Validate if a transition is allowed. Raises ValueError if not.
118
+ """
119
+ if from_status == to_status and from_stage == to_stage:
120
+ return # No change is always allowed (unless we want to enforce specific updates)
121
+
122
+ transition = self.find_transition(from_status, from_stage, to_status, to_stage, solution)
123
+
124
+ if not transition:
125
+ raise ValueError(f"Lifecycle Policy: Transition from {from_status}({from_stage if from_stage else 'None'}) "
126
+ f"to {to_status}({to_stage if to_stage else 'None'}) is not defined.")
127
+
128
+ if transition.required_solution and solution != transition.required_solution:
129
+ raise ValueError(f"Lifecycle Policy: Transition '{transition.label}' requires solution '{transition.required_solution}'.")
130
+
131
+ def enforce_policy(self, meta: IssueMetadata) -> None:
132
+ """
133
+ Apply consistency rules to IssueMetadata.
134
+ """
135
+ from ..models import current_time
136
+
137
+ if meta.status == "backlog":
138
+ meta.stage = "freezed"
139
+
140
+ elif meta.status == "closed":
141
+ if meta.stage != "done":
142
+ meta.stage = "done"
143
+ if not meta.closed_at:
144
+ meta.closed_at = current_time()
145
+
146
+ elif meta.status == "open":
147
+ if meta.stage is None:
148
+ meta.stage = "draft"
149
+
150
+ def validate_transition(self, from_status: str, from_stage: Optional[str],
151
+ to_status: str, to_stage: Optional[str],
152
+ solution: Optional[str] = None) -> None:
153
+ """
154
+ Validate if a transition is allowed. Raises ValueError if not.
155
+ """
156
+ if from_status == to_status and from_stage == to_stage:
157
+ return # No change is always allowed (unless we want to enforce specific updates)
158
+
159
+ transition = self.find_transition(from_status, from_stage, to_status, to_stage, solution)
160
+
161
+ if not transition:
162
+ raise ValueError(f"Lifecycle Policy: Transition from {from_status}({from_stage if from_stage else 'None'}) "
163
+ f"to {to_status}({to_stage if to_stage else 'None'}) is not defined.")
164
+
165
+ if transition.required_solution and solution != transition.required_solution:
166
+ raise ValueError(f"Lifecycle Policy: Transition '{transition.label}' requires solution '{transition.required_solution}'.")
167
+
168
+ def enforce_policy(self, meta: IssueMetadata) -> None:
169
+ """
170
+ Apply consistency rules to IssueMetadata.
171
+ """
172
+ from ..models import current_time
173
+
174
+ if meta.status == "backlog":
175
+ meta.stage = "freezed"
176
+
177
+ elif meta.status == "closed":
178
+ if meta.stage != "done":
179
+ meta.stage = "done"
180
+ if not meta.closed_at:
181
+ meta.closed_at = current_time()
182
+
183
+ elif meta.status == "open":
184
+ if meta.stage is None:
185
+ meta.stage = "draft"
@@ -0,0 +1,18 @@
1
+ from typing import List, Optional, Any
2
+ from pydantic import BaseModel
3
+
4
+ class Transition(BaseModel):
5
+ name: str
6
+ label: str
7
+ icon: Optional[str] = None
8
+ from_status: Optional[str] = None # None means any
9
+ from_stage: Optional[str] = None # None means any
10
+ to_status: str
11
+ to_stage: Optional[str] = None
12
+ required_solution: Optional[str] = None
13
+ description: str = ""
14
+ command_template: Optional[str] = None
15
+
16
+ class StateMachineConfig(BaseModel):
17
+ transitions: List[Transition]
18
+ # We can add more config like default stages for statuses etc.