monoco-toolkit 0.2.7__py3-none-any.whl → 0.3.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 (66) hide show
  1. monoco/cli/project.py +35 -31
  2. monoco/cli/workspace.py +26 -16
  3. monoco/core/agent/__init__.py +0 -2
  4. monoco/core/agent/action.py +44 -20
  5. monoco/core/agent/adapters.py +20 -16
  6. monoco/core/agent/protocol.py +5 -4
  7. monoco/core/agent/state.py +21 -21
  8. monoco/core/config.py +90 -33
  9. monoco/core/execution.py +21 -16
  10. monoco/core/feature.py +8 -5
  11. monoco/core/git.py +61 -30
  12. monoco/core/hooks.py +57 -0
  13. monoco/core/injection.py +47 -44
  14. monoco/core/integrations.py +50 -35
  15. monoco/core/lsp.py +12 -1
  16. monoco/core/output.py +35 -16
  17. monoco/core/registry.py +3 -2
  18. monoco/core/setup.py +190 -124
  19. monoco/core/skills.py +121 -107
  20. monoco/core/state.py +12 -10
  21. monoco/core/sync.py +85 -56
  22. monoco/core/telemetry.py +10 -6
  23. monoco/core/workspace.py +26 -19
  24. monoco/daemon/app.py +123 -79
  25. monoco/daemon/commands.py +14 -13
  26. monoco/daemon/models.py +11 -3
  27. monoco/daemon/reproduce_stats.py +8 -8
  28. monoco/daemon/services.py +32 -33
  29. monoco/daemon/stats.py +59 -40
  30. monoco/features/config/commands.py +38 -25
  31. monoco/features/i18n/adapter.py +4 -5
  32. monoco/features/i18n/commands.py +83 -49
  33. monoco/features/i18n/core.py +94 -54
  34. monoco/features/issue/adapter.py +6 -7
  35. monoco/features/issue/commands.py +500 -260
  36. monoco/features/issue/core.py +504 -293
  37. monoco/features/issue/domain/lifecycle.py +33 -23
  38. monoco/features/issue/domain/models.py +71 -38
  39. monoco/features/issue/domain/parser.py +92 -69
  40. monoco/features/issue/domain/workspace.py +19 -16
  41. monoco/features/issue/engine/__init__.py +3 -3
  42. monoco/features/issue/engine/config.py +18 -25
  43. monoco/features/issue/engine/machine.py +72 -39
  44. monoco/features/issue/engine/models.py +4 -2
  45. monoco/features/issue/linter.py +326 -111
  46. monoco/features/issue/lsp/definition.py +26 -19
  47. monoco/features/issue/migration.py +45 -34
  48. monoco/features/issue/models.py +30 -13
  49. monoco/features/issue/monitor.py +24 -8
  50. monoco/features/issue/resources/en/AGENTS.md +5 -0
  51. monoco/features/issue/resources/en/SKILL.md +30 -2
  52. monoco/features/issue/resources/zh/AGENTS.md +5 -0
  53. monoco/features/issue/resources/zh/SKILL.md +26 -1
  54. monoco/features/issue/validator.py +417 -172
  55. monoco/features/skills/__init__.py +0 -1
  56. monoco/features/skills/core.py +24 -18
  57. monoco/features/spike/adapter.py +4 -5
  58. monoco/features/spike/commands.py +51 -38
  59. monoco/features/spike/core.py +24 -16
  60. monoco/main.py +34 -21
  61. {monoco_toolkit-0.2.7.dist-info → monoco_toolkit-0.3.0.dist-info}/METADATA +10 -3
  62. monoco_toolkit-0.3.0.dist-info/RECORD +84 -0
  63. monoco_toolkit-0.2.7.dist-info/RECORD +0 -83
  64. {monoco_toolkit-0.2.7.dist-info → monoco_toolkit-0.3.0.dist-info}/WHEEL +0 -0
  65. {monoco_toolkit-0.2.7.dist-info → monoco_toolkit-0.3.0.dist-info}/entry_points.txt +0 -0
  66. {monoco_toolkit-0.2.7.dist-info → monoco_toolkit-0.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -16,7 +16,7 @@ from pydantic import BaseModel, Field
16
16
  class AgentIntegration(BaseModel):
17
17
  """
18
18
  Configuration for a single agent framework integration.
19
-
19
+
20
20
  Attributes:
21
21
  key: Unique identifier for the framework (e.g., 'cursor', 'gemini')
22
22
  name: Human-readable name of the framework
@@ -26,13 +26,22 @@ class AgentIntegration(BaseModel):
26
26
  version_cmd: Optional command to check version (e.g., '--version')
27
27
  enabled: Whether this integration is active (default: True)
28
28
  """
29
+
29
30
  key: str = Field(..., description="Unique framework identifier")
30
31
  name: str = Field(..., description="Human-readable framework name")
31
- system_prompt_file: str = Field(..., description="Path to system prompt file (relative to project root)")
32
- skill_root_dir: str = Field(..., description="Path to skills directory (relative to project root)")
33
- bin_name: Optional[str] = Field(None, description="Binary name to check for availability")
32
+ system_prompt_file: str = Field(
33
+ ..., description="Path to system prompt file (relative to project root)"
34
+ )
35
+ skill_root_dir: str = Field(
36
+ ..., description="Path to skills directory (relative to project root)"
37
+ )
38
+ bin_name: Optional[str] = Field(
39
+ None, description="Binary name to check for availability"
40
+ )
34
41
  version_cmd: Optional[str] = Field(None, description="Command to check version")
35
- enabled: bool = Field(default=True, description="Whether this integration is active")
42
+ enabled: bool = Field(
43
+ default=True, description="Whether this integration is active"
44
+ )
36
45
 
37
46
  def check_health(self) -> "AgentProviderHealth":
38
47
  """
@@ -43,14 +52,18 @@ class AgentIntegration(BaseModel):
43
52
  import time
44
53
 
45
54
  if not self.bin_name:
46
- return AgentProviderHealth(available=True) # If no binary required, assume ok
55
+ return AgentProviderHealth(
56
+ available=True
57
+ ) # If no binary required, assume ok
47
58
 
48
59
  bin_path = shutil.which(self.bin_name)
49
60
  if not bin_path:
50
- return AgentProviderHealth(available=False, error=f"Binary '{self.bin_name}' not found in PATH")
61
+ return AgentProviderHealth(
62
+ available=False, error=f"Binary '{self.bin_name}' not found in PATH"
63
+ )
51
64
 
52
65
  if not self.version_cmd:
53
- return AgentProviderHealth(available=True, path=bin_path)
66
+ return AgentProviderHealth(available=True, path=bin_path)
54
67
 
55
68
  start_time = time.time()
56
69
  try:
@@ -60,16 +73,19 @@ class AgentIntegration(BaseModel):
60
73
  [self.bin_name] + self.version_cmd.split(),
61
74
  check=True,
62
75
  capture_output=True,
63
- timeout=5
76
+ timeout=5,
64
77
  )
65
78
  latency = int((time.time() - start_time) * 1000)
66
- return AgentProviderHealth(available=True, path=bin_path, latency_ms=latency)
79
+ return AgentProviderHealth(
80
+ available=True, path=bin_path, latency_ms=latency
81
+ )
67
82
  except Exception as e:
68
83
  return AgentProviderHealth(available=False, path=bin_path, error=str(e))
69
84
 
70
85
 
71
86
  class AgentProviderHealth(BaseModel):
72
87
  """Health check result for an agent provider."""
88
+
73
89
  available: bool
74
90
  path: Optional[str] = None
75
91
  error: Optional[str] = None
@@ -128,19 +144,18 @@ DEFAULT_INTEGRATIONS: Dict[str, AgentIntegration] = {
128
144
 
129
145
 
130
146
  def get_integration(
131
- name: str,
132
- config_overrides: Optional[Dict[str, AgentIntegration]] = None
147
+ name: str, config_overrides: Optional[Dict[str, AgentIntegration]] = None
133
148
  ) -> Optional[AgentIntegration]:
134
149
  """
135
150
  Get an agent integration by name.
136
-
151
+
137
152
  Args:
138
153
  name: The framework key (e.g., 'cursor', 'gemini')
139
154
  config_overrides: Optional user-defined integrations from config
140
-
155
+
141
156
  Returns:
142
157
  AgentIntegration if found, None otherwise
143
-
158
+
144
159
  Priority:
145
160
  1. User config overrides
146
161
  2. Default registry
@@ -148,52 +163,52 @@ def get_integration(
148
163
  # Check user overrides first
149
164
  if config_overrides and name in config_overrides:
150
165
  return config_overrides[name]
151
-
166
+
152
167
  # Fall back to defaults
153
168
  return DEFAULT_INTEGRATIONS.get(name)
154
169
 
155
170
 
156
171
  def get_all_integrations(
157
172
  config_overrides: Optional[Dict[str, AgentIntegration]] = None,
158
- enabled_only: bool = True
173
+ enabled_only: bool = True,
159
174
  ) -> Dict[str, AgentIntegration]:
160
175
  """
161
176
  Get all available integrations.
162
-
177
+
163
178
  Args:
164
179
  config_overrides: Optional user-defined integrations from config
165
180
  enabled_only: If True, only return enabled integrations
166
-
181
+
167
182
  Returns:
168
183
  Dictionary of all integrations (merged defaults + overrides)
169
184
  """
170
185
  # Start with defaults
171
186
  all_integrations = DEFAULT_INTEGRATIONS.copy()
172
-
187
+
173
188
  # Merge user overrides
174
189
  if config_overrides:
175
190
  all_integrations.update(config_overrides)
176
-
191
+
177
192
  # Filter by enabled status if requested
178
193
  if enabled_only:
179
194
  return {k: v for k, v in all_integrations.items() if v.enabled}
180
-
195
+
181
196
  return all_integrations
182
197
 
183
198
 
184
199
  def detect_frameworks(root: Path) -> List[str]:
185
200
  """
186
201
  Auto-detect which agent frameworks are present in the project.
187
-
202
+
188
203
  Detection is based on the existence of characteristic files/directories
189
204
  for each framework.
190
-
205
+
191
206
  Args:
192
207
  root: Project root directory
193
-
208
+
194
209
  Returns:
195
210
  List of detected framework keys (e.g., ['cursor', 'gemini'])
196
-
211
+
197
212
  Example:
198
213
  >>> root = Path("/path/to/project")
199
214
  >>> frameworks = detect_frameworks(root)
@@ -201,42 +216,42 @@ def detect_frameworks(root: Path) -> List[str]:
201
216
  ['cursor', 'gemini']
202
217
  """
203
218
  detected = []
204
-
219
+
205
220
  for key, integration in DEFAULT_INTEGRATIONS.items():
206
221
  # Check if system prompt file exists
207
222
  prompt_file = root / integration.system_prompt_file
208
-
223
+
209
224
  # Check if skill directory exists
210
225
  skill_dir = root / integration.skill_root_dir
211
-
226
+
212
227
  # Consider framework present if either exists
213
228
  if prompt_file.exists() or skill_dir.exists():
214
229
  detected.append(key)
215
-
230
+
216
231
  return detected
217
232
 
218
233
 
219
234
  def get_active_integrations(
220
235
  root: Path,
221
236
  config_overrides: Optional[Dict[str, AgentIntegration]] = None,
222
- auto_detect: bool = True
237
+ auto_detect: bool = True,
223
238
  ) -> Dict[str, AgentIntegration]:
224
239
  """
225
240
  Get integrations that are both enabled and detected in the project.
226
-
241
+
227
242
  Args:
228
243
  root: Project root directory
229
244
  config_overrides: Optional user-defined integrations from config
230
245
  auto_detect: If True, only return integrations detected in the project
231
-
246
+
232
247
  Returns:
233
248
  Dictionary of active integrations
234
249
  """
235
250
  all_integrations = get_all_integrations(config_overrides, enabled_only=True)
236
-
251
+
237
252
  if not auto_detect:
238
253
  return all_integrations
239
-
254
+
240
255
  # Filter by detection
241
256
  detected_keys = detect_frameworks(root)
242
257
  return {k: v for k, v in all_integrations.items() if k in detected_keys}
monoco/core/lsp.py CHANGED
@@ -2,10 +2,12 @@ from enum import IntEnum
2
2
  from typing import List, Optional, Union
3
3
  from pydantic import BaseModel
4
4
 
5
+
5
6
  class Position(BaseModel):
6
7
  """
7
8
  Position in a text document expressed as zero-based line and character offset.
8
9
  """
10
+
9
11
  line: int
10
12
  character: int
11
13
 
@@ -14,40 +16,49 @@ class Position(BaseModel):
14
16
  return self.line < other.line
15
17
  return self.character < other.character
16
18
 
19
+
17
20
  class Range(BaseModel):
18
21
  """
19
22
  A range in a text document expressed as (zero-based) start and end positions.
20
23
  """
24
+
21
25
  start: Position
22
26
  end: Position
23
27
 
24
28
  def __repr__(self):
25
29
  return f"{self.start.line}:{self.start.character}-{self.end.line}:{self.end.character}"
26
30
 
31
+
27
32
  class Location(BaseModel):
28
33
  """
29
34
  Represents a location inside a resource, such as a line of code inside a text file.
30
35
  """
36
+
31
37
  uri: str
32
38
  range: Range
33
39
 
40
+
34
41
  class DiagnosticSeverity(IntEnum):
35
42
  Error = 1
36
43
  Warning = 2
37
44
  Information = 3
38
45
  Hint = 4
39
46
 
47
+
40
48
  class DiagnosticRelatedInformation(BaseModel):
41
49
  """
42
50
  Represents a related message and source code location for a diagnostic.
43
51
  """
52
+
44
53
  # location: Location # Defined elsewhere or simplified here
45
54
  message: str
46
55
 
56
+
47
57
  class Diagnostic(BaseModel):
48
58
  """
49
59
  Represents a diagnostic, such as a compiler error or warning.
50
60
  """
61
+
51
62
  range: Range
52
63
  severity: Optional[DiagnosticSeverity] = None
53
64
  code: Optional[Union[int, str]] = None
@@ -62,7 +73,7 @@ class Diagnostic(BaseModel):
62
73
  1: "[red]Error[/red]",
63
74
  2: "[yellow]Warning[/yellow]",
64
75
  3: "[blue]Info[/blue]",
65
- 4: "[dim]Hint[/dim]"
76
+ 4: "[dim]Hint[/dim]",
66
77
  }
67
78
  sev = severity_map.get(self.severity, "Error")
68
79
  return f"{sev}: {self.message} (Line {self.range.start.line + 1})"
monoco/core/output.py CHANGED
@@ -7,12 +7,20 @@ from rich.console import Console
7
7
  from rich.table import Table
8
8
  from rich import print as rprint
9
9
 
10
+
10
11
  def _set_agent_mode(value: bool):
11
12
  if value:
12
13
  os.environ["AGENT_FLAG"] = "true"
13
14
 
15
+
14
16
  # Reusable dependency for commands
15
- AgentOutput = Annotated[bool, typer.Option("--json", help="Output in compact JSON for Agents", callback=_set_agent_mode)]
17
+ AgentOutput = Annotated[
18
+ bool,
19
+ typer.Option(
20
+ "--json", help="Output in compact JSON for Agents", callback=_set_agent_mode
21
+ ),
22
+ ]
23
+
16
24
 
17
25
  class OutputManager:
18
26
  """
@@ -27,11 +35,14 @@ class OutputManager:
27
35
  1. Environment variable AGENT_FLAG=true (or 1)
28
36
  2. Environment variable MONOCO_AGENT=true (or 1)
29
37
  """
30
- return os.getenv("AGENT_FLAG", "").lower() in ("true", "1") or \
31
- os.getenv("MONOCO_AGENT", "").lower() in ("true", "1")
38
+ return os.getenv("AGENT_FLAG", "").lower() in ("true", "1") or os.getenv(
39
+ "MONOCO_AGENT", ""
40
+ ).lower() in ("true", "1")
32
41
 
33
42
  @staticmethod
34
- def print(data: Union[BaseModel, List[BaseModel], dict, list, str], title: str = ""):
43
+ def print(
44
+ data: Union[BaseModel, List[BaseModel], dict, list, str], title: str = ""
45
+ ):
35
46
  """
36
47
  Dual frontend dispatcher.
37
48
  """
@@ -58,20 +69,27 @@ class OutputManager:
58
69
  """
59
70
  if isinstance(data, BaseModel):
60
71
  print(data.model_dump_json(exclude_none=True))
61
- elif isinstance(data, list) and all(isinstance(item, BaseModel) for item in data):
72
+ elif isinstance(data, list) and all(
73
+ isinstance(item, BaseModel) for item in data
74
+ ):
62
75
  # Pydantic v2 adapter for list of models
63
- print(json.dumps([item.model_dump(mode='json', exclude_none=True) for item in data], separators=(',', ':')))
76
+ print(
77
+ json.dumps(
78
+ [item.model_dump(mode="json", exclude_none=True) for item in data],
79
+ separators=(",", ":"),
80
+ )
81
+ )
64
82
  else:
65
83
  # Fallback for dicts/lists/primitives
66
84
  def _encoder(obj):
67
85
  if isinstance(obj, BaseModel):
68
- return obj.model_dump(mode='json', exclude_none=True)
69
- if hasattr(obj, 'value'): # Enum support
70
- return obj.value
86
+ return obj.model_dump(mode="json", exclude_none=True)
87
+ if hasattr(obj, "value"): # Enum support
88
+ return obj.value
71
89
  return str(obj)
72
90
 
73
91
  try:
74
- print(json.dumps(data, separators=(',', ':'), default=_encoder))
92
+ print(json.dumps(data, separators=(",", ":"), default=_encoder))
75
93
  except TypeError:
76
94
  print(str(data))
77
95
 
@@ -81,7 +99,7 @@ class OutputManager:
81
99
  Human channel: Visual priority.
82
100
  """
83
101
  console = Console()
84
-
102
+
85
103
  if title:
86
104
  console.rule(f"[bold blue]{title}[/bold blue]")
87
105
 
@@ -92,24 +110,25 @@ class OutputManager:
92
110
  # Special handling for Lists of Pydantic Models -> Table
93
111
  if isinstance(data, list) and data and isinstance(data[0], BaseModel):
94
112
  table = Table(show_header=True, header_style="bold magenta")
95
-
113
+
96
114
  # Introspect fields from the first item
97
115
  model_type = type(data[0])
98
116
  fields = model_type.model_fields.keys()
99
-
117
+
100
118
  for field in fields:
101
119
  table.add_column(field.replace("_", " ").title())
102
-
120
+
103
121
  for item in data:
104
122
  row = [str(getattr(item, field)) for field in fields]
105
123
  table.add_row(*row)
106
-
124
+
107
125
  console.print(table)
108
126
  return
109
-
127
+
110
128
  # Fallback to rich pretty print
111
129
  rprint(data)
112
130
 
131
+
113
132
  # Global helper
114
133
  print_output = OutputManager.print
115
134
  print_error = OutputManager.error
monoco/core/registry.py CHANGED
@@ -1,6 +1,7 @@
1
- from typing import Dict, List, Type
1
+ from typing import Dict, List
2
2
  from monoco.core.feature import MonocoFeature
3
3
 
4
+
4
5
  class FeatureRegistry:
5
6
  _features: Dict[str, MonocoFeature] = {}
6
7
 
@@ -29,7 +30,7 @@ class FeatureRegistry:
29
30
  from monoco.features.issue.adapter import IssueFeature
30
31
  from monoco.features.spike.adapter import SpikeFeature
31
32
  from monoco.features.i18n.adapter import I18nFeature
32
-
33
+
33
34
  cls.register(IssueFeature())
34
35
  cls.register(SpikeFeature())
35
36
  cls.register(I18nFeature())