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
monoco/core/setup.py CHANGED
@@ -1,26 +1,35 @@
1
1
  import os
2
2
  import subprocess
3
+ import sys
3
4
  import yaml
4
5
  from pathlib import Path
5
6
  from typing import Optional
7
+
6
8
  import typer
9
+ from prompt_toolkit.application import Application
10
+ from prompt_toolkit.layout.containers import Window, HSplit
11
+ from prompt_toolkit.layout.controls import FormattedTextControl
12
+ from prompt_toolkit.layout.layout import Layout
13
+ from prompt_toolkit.key_binding import KeyBindings
14
+ from prompt_toolkit.styles import Style
7
15
  from rich.console import Console
8
- from monoco.core.output import print_output
9
16
 
10
17
  console = Console()
11
18
 
19
+
12
20
  def get_git_user() -> str:
13
21
  try:
14
22
  result = subprocess.run(
15
- ["git", "config", "user.name"],
16
- capture_output=True,
17
- text=True,
18
- timeout=1
23
+ ["git", "config", "user.name"],
24
+ capture_output=True,
25
+ text=True,
26
+ timeout=1,
19
27
  )
20
28
  return result.stdout.strip()
21
29
  except Exception:
22
30
  return ""
23
31
 
32
+
24
33
  def generate_key(name: str) -> str:
25
34
  """Generate a 3-4 letter uppercase key from name."""
26
35
  # Strategy 1: Upper case of first letters of words
@@ -29,128 +38,147 @@ def generate_key(name: str) -> str:
29
38
  candidate = "".join(p[0] for p in parts[:4]).upper()
30
39
  if len(candidate) >= 2:
31
40
  return candidate
32
-
41
+
33
42
  # Strategy 2: First 3 letters
34
43
  return name[:3].upper()
35
44
 
36
- from prompt_toolkit.application import Application
37
- from prompt_toolkit.layout.containers import Window, HSplit
38
- from prompt_toolkit.layout.controls import FormattedTextControl
39
- from prompt_toolkit.layout.layout import Layout
40
- from prompt_toolkit.key_binding import KeyBindings
41
- from prompt_toolkit.styles import Style
42
- import sys
43
45
 
44
46
  def ask_with_selection(message: str, default: str) -> str:
45
47
  """Provides a selection-based prompt for stable rendering."""
46
48
  options = [f"{default} (Default)", "Custom Input..."]
47
49
  selected_index = 0
48
-
50
+
49
51
  kb = KeyBindings()
50
-
51
- @kb.add('up')
52
- @kb.add('k')
52
+
53
+ @kb.add("up")
54
+ @kb.add("k")
53
55
  def _(event):
54
56
  nonlocal selected_index
55
57
  selected_index = (selected_index - 1) % len(options)
56
58
 
57
- @kb.add('down')
58
- @kb.add('j')
59
+ @kb.add("down")
60
+ @kb.add("j")
59
61
  def _(event):
60
62
  nonlocal selected_index
61
63
  selected_index = (selected_index + 1) % len(options)
62
64
 
63
- @kb.add('enter')
65
+ @kb.add("enter")
64
66
  def _(event):
65
67
  event.app.exit(result=selected_index)
66
68
 
67
- @kb.add('c-c')
69
+ @kb.add("c-c")
68
70
  def _(event):
69
71
  console.print("\n[red]Aborted by user.[/red]")
70
72
  sys.exit(0)
71
73
 
72
74
  def get_text():
73
75
  # Render the menu with explicit highlighting
74
- res = [('class:message', f"{message}:\n")]
76
+ res = [("class:message", f"{message}:\n")]
75
77
  for i, opt in enumerate(options):
76
78
  if i == selected_index:
77
- res.append(('class:selected', f" ➔ {opt}\n"))
79
+ res.append(("class:selected", f" ➔ {opt}\n"))
78
80
  else:
79
- res.append(('class:unselected', f" {opt}\n"))
81
+ res.append(("class:unselected", f" {opt}\n"))
80
82
  return res
81
83
 
82
- style = Style.from_dict({
83
- 'message': 'bold #ffffff',
84
- 'selected': 'bold #00ff00', # High contrast green
85
- 'unselected': '#888888',
86
- })
84
+ style = Style.from_dict(
85
+ {
86
+ "message": "bold #ffffff",
87
+ "selected": "bold #00ff00", # High contrast green
88
+ "unselected": "#888888",
89
+ }
90
+ )
87
91
 
88
92
  # Run a mini application to handle the selection
89
93
  app = Application(
90
- layout=Layout(HSplit([Window(content=FormattedTextControl(get_text), height=len(options)+1)])),
94
+ layout=Layout(
95
+ HSplit(
96
+ [
97
+ Window(
98
+ content=FormattedTextControl(get_text), height=len(options) + 1
99
+ )
100
+ ]
101
+ )
102
+ ),
91
103
  key_bindings=kb,
92
104
  style=style,
93
105
  full_screen=False,
94
106
  )
95
-
107
+
96
108
  # Flush stdout to ensure previous output is visible
97
109
  sys.stdout.flush()
98
-
110
+
99
111
  choice = app.run()
100
-
112
+
101
113
  if choice == 0:
102
114
  return default
103
115
  else:
104
116
  # Prompt for custom input
105
117
  from prompt_toolkit import prompt
118
+
106
119
  return prompt(f"Enter custom {message.lower()}: ").strip() or default
107
120
 
121
+
108
122
  def init_cli(
109
- ctx: typer.Context,
110
- global_only: bool = typer.Option(False, "--global", help="Only configure global user settings"),
111
- project_only: bool = typer.Option(False, "--project", help="Only configure current project"),
123
+ ctx: typer.Context,
124
+ global_only: bool = typer.Option(
125
+ False, "--global", help="Only configure global user settings"
126
+ ),
127
+ project_only: bool = typer.Option(
128
+ False, "--project", help="Only configure current project"
129
+ ),
112
130
  # Non-interactive arguments
113
131
  name: Optional[str] = typer.Option(None, "--name", "-n", help="Project Name"),
114
132
  key: Optional[str] = typer.Option(None, "--key", "-k", help="Project Key"),
115
133
  author: Optional[str] = typer.Option(None, "--author", "-a", help="Author Name"),
116
- telemetry: Optional[bool] = typer.Option(None, "--telemetry/--no-telemetry", help="Enable/Disable telemetry")
134
+ telemetry: Optional[bool] = typer.Option(
135
+ None, "--telemetry/--no-telemetry", help="Enable/Disable telemetry"
136
+ ),
117
137
  ):
118
138
  """
119
139
  Initialize Monoco configuration (Global and/or Project).
120
140
  """
121
141
  # Force non-interactive for now as requested
122
142
  interactive = False
123
-
143
+
124
144
  home_dir = Path.home() / ".monoco"
125
145
  global_config_path = home_dir / "config.yaml"
126
-
146
+
127
147
  # --- 1. Global Configuration ---
128
148
  if not project_only:
129
149
  if not global_config_path.exists() or global_only:
130
150
  console.rule("[bold blue]Global Setup[/bold blue]")
131
-
151
+
132
152
  # Ensure ~/.monoco exists
133
153
  home_dir.mkdir(parents=True, exist_ok=True)
134
-
154
+
135
155
  default_author = get_git_user() or os.getenv("USER", "developer")
136
-
156
+
137
157
  if author is None:
138
158
  if interactive:
139
- author = ask_with_selection("Your Name (for issue tracking)", default_author)
159
+ author = ask_with_selection(
160
+ "Your Name (for issue tracking)", default_author
161
+ )
140
162
  else:
141
- # Fallback or Error?
163
+ # Fallback or Error?
142
164
  # For global author, we can use default if not provided, or error?
143
165
  # User said "Directly error saying what field is missing"
144
166
  # But author has a reasonable default. Let's try to use default if available, else error.
145
167
  if not default_author:
146
- console.print("[red]Error:[/red] Missing required field: --author")
147
- raise typer.Exit(code=1)
168
+ console.print(
169
+ "[red]Error:[/red] Missing required field: --author"
170
+ )
171
+ raise typer.Exit(code=1)
148
172
  author = default_author
149
173
 
150
174
  if telemetry is None:
151
175
  if interactive:
152
176
  from rich.prompt import Confirm
153
- telemetry = Confirm.ask("Enable anonymous telemetry to help improve Monoco?", default=True)
177
+
178
+ telemetry = Confirm.ask(
179
+ "Enable anonymous telemetry to help improve Monoco?",
180
+ default=True,
181
+ )
154
182
  else:
155
183
  # Default to True or False? Let's default to False for non-interactive safety or True?
156
184
  # Usually explicit is better. Let's assume False if not specified in non-interactive.
@@ -163,15 +191,15 @@ def init_cli(
163
191
  "core": {
164
192
  "author": author,
165
193
  },
166
- "telemetry": {
167
- "enabled": telemetry
168
- }
194
+ "telemetry": {"enabled": telemetry},
169
195
  }
170
-
196
+
171
197
  with open(global_config_path, "w") as f:
172
198
  yaml.dump(user_config, f, default_flow_style=False)
173
-
174
- console.print(f"[green]✓ Global config saved to {global_config_path}[/green]\n")
199
+
200
+ console.print(
201
+ f"[green]✓ Global config saved to {global_config_path}[/green]\n"
202
+ )
175
203
 
176
204
  if global_only:
177
205
  return
@@ -181,91 +209,129 @@ def init_cli(
181
209
  project_config_dir = cwd / ".monoco"
182
210
  workspace_config_path = project_config_dir / "workspace.yaml"
183
211
  project_config_path = project_config_dir / "project.yaml"
184
-
212
+
213
+ project_initialized = False
214
+
185
215
  # Check if we should init project
186
216
  if workspace_config_path.exists() or project_config_path.exists():
187
217
  if interactive:
188
218
  from rich.prompt import Confirm
189
- if not Confirm.ask(f"Project/Workspace config already exists in [dim]{project_config_dir}[/dim]. Overwrite?"):
190
- console.print("[yellow]Skipping project initialization.[/yellow]")
191
- return
192
- else:
193
- console.print(f"[yellow]Project/Workspace config already exists in {project_config_dir}. Use manual edit or delete it to re-init.[/yellow]")
194
- return
195
-
196
- console.rule("[bold blue]Project Setup[/bold blue]")
197
-
198
- default_name = cwd.name
199
-
200
- if name is None:
201
- if interactive:
202
- name = ask_with_selection("Project Name", default_name)
219
+
220
+ if not Confirm.ask(
221
+ f"Project/Workspace config already exists in [dim]{project_config_dir}[/dim]. Overwrite?"
222
+ ):
223
+ console.print("[dim]Skipping configuration overwrite.[/dim]")
224
+ project_initialized = True
203
225
  else:
204
- console.print("[red]Error:[/red] Missing required field: --name")
205
- raise typer.Exit(code=1)
206
-
207
- project_name = name
208
-
209
- default_key = generate_key(project_name)
210
-
211
- if key is None:
212
- if interactive:
213
- key = ask_with_selection("Project Key (prefix for issues)", default_key)
226
+ console.print(
227
+ f"[dim]Project/Workspace config already exists in {project_config_dir}. skipping generation.[/dim]"
228
+ )
229
+ project_initialized = True
230
+
231
+ # Load existing config for downstream usage
232
+ if workspace_config_path.exists():
233
+ try:
234
+ with open(workspace_config_path, "r") as f:
235
+ workspace_config = yaml.safe_load(f) or {}
236
+ except Exception:
237
+ workspace_config = {}
214
238
  else:
215
- console.print("[red]Error:[/red] Missing required field: --key")
216
- raise typer.Exit(code=1)
217
-
218
- project_key = key
219
-
220
-
221
- project_config_dir.mkdir(exist_ok=True)
222
-
223
- # 2a. Create project.yaml (Identity)
224
- project_config = {
225
- "project": {
226
- "name": project_name,
227
- "key": project_key
228
- }
229
- }
230
-
231
- with open(project_config_path, "w") as f:
232
- yaml.dump(project_config, f, default_flow_style=False)
233
-
234
- # 2b. Create workspace.yaml (Environment)
235
- workspace_config = {
236
- "paths": {
237
- "issues": "Issues",
238
- "spikes": ".references",
239
- "specs": "SPECS"
239
+ workspace_config = {}
240
+
241
+ if not project_initialized:
242
+ console.rule("[bold blue]Project Setup[/bold blue]")
243
+
244
+ default_name = cwd.name
245
+
246
+ if name is None:
247
+ if interactive:
248
+ name = ask_with_selection("Project Name", default_name)
249
+ else:
250
+ console.print("[red]Error:[/red] Missing required field: --name")
251
+ raise typer.Exit(code=1)
252
+
253
+ project_name = name
254
+
255
+ default_key = generate_key(project_name)
256
+
257
+ if key is None:
258
+ if interactive:
259
+ key = ask_with_selection("Project Key (prefix for issues)", default_key)
260
+ else:
261
+ console.print("[red]Error:[/red] Missing required field: --key")
262
+ raise typer.Exit(code=1)
263
+
264
+ project_key = key
265
+
266
+ project_config_dir.mkdir(exist_ok=True)
267
+
268
+ # 2a. Create project.yaml (Identity)
269
+ project_config = {"project": {"name": project_name, "key": project_key}}
270
+
271
+ with open(project_config_path, "w") as f:
272
+ yaml.dump(project_config, f, default_flow_style=False)
273
+
274
+ # 2b. Create workspace.yaml (Environment)
275
+ workspace_config = {
276
+ "paths": {"issues": "Issues", "spikes": ".references"},
277
+ "hooks": {"pre-commit": "monoco issue lint --recursive"},
240
278
  }
241
- }
242
-
243
- with open(workspace_config_path, "w") as f:
244
- yaml.dump(workspace_config, f, default_flow_style=False)
245
-
246
- # 2c. Generate Config Template (Optional - might need update)
247
- # For now, let's skip template generation or update it later.
248
- # Or generate a workspace_template.yaml
249
-
250
- console.print(f"[green] Project initialized in {cwd}[/green]")
251
- console.print(f"[dim] - Identity: .monoco/project.yaml[/dim]")
252
- console.print(f"[dim] - Environment: .monoco/workspace.yaml[/dim]")
279
+
280
+ with open(workspace_config_path, "w") as f:
281
+ yaml.dump(workspace_config, f, default_flow_style=False)
282
+
283
+ # 2c. Generate Config Template (Optional - might need update)
284
+ # For now, let's skip template generation or update it later.
285
+ # Or generate a workspace_template.yaml
286
+
287
+ console.print(f"[green]✓ Project initialized in {cwd}[/green]")
288
+ console.print("[dim] - Identity: .monoco/project.yaml[/dim]")
289
+ console.print("[dim] - Environment: .monoco/workspace.yaml[/dim]")
290
+
291
+ # Common Post-Init Logic (Idempotent)
292
+
293
+ # Retrieve project key if we skipped initialization
294
+ if project_initialized:
295
+ # Try to read project key from file or just fallback
296
+ if project_config_path.exists():
297
+ try:
298
+ with open(project_config_path, "r") as f:
299
+ p_conf = yaml.safe_load(f)
300
+ project_key = p_conf.get("project", {}).get("key", "MON")
301
+ except:
302
+ project_key = "MON"
303
+ else:
304
+ project_key = "MON"
253
305
 
254
306
  # Check for issue feature init (this logic was implicit in caller?)
255
307
  # No, init_cli is the main logic.
256
-
308
+
257
309
  # Initialize basic directories
258
310
  (cwd / "Issues").mkdir(exist_ok=True)
259
311
  (cwd / ".references").mkdir(exist_ok=True)
260
-
261
- # Initialize Agent Resources
312
+
313
+ # Initialize Agent Resources (Deprecated)
314
+ # Removing reference to monoco.features.agent
315
+ # try:
316
+ # from monoco.features.agent.core import init_agent_resources
317
+ # init_agent_resources(cwd)
318
+ # console.print(f"[dim] - Agent Resources: .monoco/actions/*.prompty[/dim]")
319
+ # except Exception as e:
320
+ # console.print(f"[yellow]Warning: Failed to init agent resources: {e}[/yellow]")
321
+
322
+ # Initialize Hooks
262
323
  try:
263
- from monoco.features.agent.core import init_agent_resources
264
- init_agent_resources(cwd)
265
- console.print(f"[dim] - Agent Resources: .monoco/actions/*.prompty[/dim]")
324
+ from monoco.core.hooks import install_hooks
325
+
326
+ # Re-load config to get the just-written hooks (or default ones)
327
+ # Actually we have the dict right here in workspace_config['hooks']
328
+ hooks_config = workspace_config.get("hooks", {})
329
+ if hooks_config:
330
+ install_hooks(cwd, hooks_config)
266
331
  except Exception as e:
267
- console.print(f"[yellow]Warning: Failed to init agent resources: {e}[/yellow]")
332
+ console.print(f"[yellow]Warning: Failed to install hooks: {e}[/yellow]")
268
333
 
269
334
  console.print("\n[bold green]✓ Monoco Project Initialized![/bold green]")
270
- console.print(f"Access configured! issues will be created as [bold]{project_key}-XXX[/bold]")
271
-
335
+ console.print(
336
+ f"Access configured! issues will be created as [bold]{project_key}-XXX[/bold]"
337
+ )