monoco-toolkit 0.1.4__py3-none-any.whl → 0.1.6__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 (43) hide show
  1. monoco/core/config.py +60 -8
  2. monoco/core/feature.py +58 -0
  3. monoco/core/injection.py +196 -0
  4. monoco/core/integrations.py +181 -0
  5. monoco/core/registry.py +36 -0
  6. monoco/core/resources/en/AGENTS.md +8 -0
  7. monoco/core/resources/en/SKILL.md +66 -0
  8. monoco/core/resources/zh/AGENTS.md +8 -0
  9. monoco/core/resources/zh/SKILL.md +66 -0
  10. monoco/core/setup.py +40 -24
  11. monoco/core/skills.py +444 -0
  12. monoco/core/sync.py +224 -0
  13. monoco/core/workspace.py +2 -6
  14. monoco/daemon/services.py +1 -1
  15. monoco/features/config/commands.py +104 -44
  16. monoco/features/i18n/adapter.py +29 -0
  17. monoco/features/i18n/core.py +1 -11
  18. monoco/features/i18n/resources/en/AGENTS.md +8 -0
  19. monoco/features/i18n/resources/en/SKILL.md +94 -0
  20. monoco/features/i18n/resources/zh/AGENTS.md +8 -0
  21. monoco/features/i18n/resources/zh/SKILL.md +94 -0
  22. monoco/features/issue/adapter.py +34 -0
  23. monoco/features/issue/commands.py +8 -8
  24. monoco/features/issue/core.py +5 -16
  25. monoco/features/issue/migration.py +134 -0
  26. monoco/features/issue/models.py +5 -3
  27. monoco/features/issue/resources/en/AGENTS.md +9 -0
  28. monoco/features/issue/resources/en/SKILL.md +51 -0
  29. monoco/features/issue/resources/zh/AGENTS.md +9 -0
  30. monoco/features/issue/resources/zh/SKILL.md +85 -0
  31. monoco/features/spike/adapter.py +30 -0
  32. monoco/features/spike/core.py +3 -20
  33. monoco/features/spike/resources/en/AGENTS.md +7 -0
  34. monoco/features/spike/resources/en/SKILL.md +74 -0
  35. monoco/features/spike/resources/zh/AGENTS.md +7 -0
  36. monoco/features/spike/resources/zh/SKILL.md +74 -0
  37. monoco/main.py +4 -0
  38. {monoco_toolkit-0.1.4.dist-info → monoco_toolkit-0.1.6.dist-info}/METADATA +1 -1
  39. monoco_toolkit-0.1.6.dist-info/RECORD +62 -0
  40. monoco_toolkit-0.1.4.dist-info/RECORD +0 -36
  41. {monoco_toolkit-0.1.4.dist-info → monoco_toolkit-0.1.6.dist-info}/WHEEL +0 -0
  42. {monoco_toolkit-0.1.4.dist-info → monoco_toolkit-0.1.6.dist-info}/entry_points.txt +0 -0
  43. {monoco_toolkit-0.1.4.dist-info → monoco_toolkit-0.1.6.dist-info}/licenses/LICENSE +0 -0
monoco/core/config.py CHANGED
@@ -2,8 +2,18 @@ import os
2
2
  import yaml
3
3
  from pathlib import Path
4
4
  from typing import Optional, Dict, Any
5
+ from enum import Enum
5
6
  from pydantic import BaseModel, Field
6
7
 
8
+ import logging
9
+
10
+ # Import AgentIntegration for type hints
11
+ from typing import TYPE_CHECKING
12
+ if TYPE_CHECKING:
13
+ from monoco.core.integrations import AgentIntegration
14
+
15
+ logger = logging.getLogger("monoco.core.config")
16
+
7
17
  class PathsConfig(BaseModel):
8
18
  """Configuration for directory paths."""
9
19
  root: str = Field(default=".", description="Project root directory")
@@ -37,6 +47,16 @@ class TelemetryConfig(BaseModel):
37
47
  """Configuration for Telemetry."""
38
48
  enabled: Optional[bool] = Field(default=None, description="Whether telemetry is enabled")
39
49
 
50
+ class AgentConfig(BaseModel):
51
+ """Configuration for Agent Environment Integration."""
52
+ targets: Optional[list[str]] = Field(default=None, description="Specific target files to inject into (e.g. .cursorrules)")
53
+ framework: Optional[str] = Field(default=None, description="Manually specified agent framework (cursor, windsurf, etc.)")
54
+ includes: Optional[list[str]] = Field(default=None, description="List of specific features to include in injection")
55
+ integrations: Optional[Dict[str, Any]] = Field(
56
+ default=None,
57
+ description="Custom agent framework integrations (overrides defaults from monoco.core.integrations)"
58
+ )
59
+
40
60
  class MonocoConfig(BaseModel):
41
61
  """
42
62
  Main Configuration Schema.
@@ -48,6 +68,7 @@ class MonocoConfig(BaseModel):
48
68
  i18n: I18nConfig = Field(default_factory=I18nConfig)
49
69
  ui: UIConfig = Field(default_factory=UIConfig)
50
70
  telemetry: TelemetryConfig = Field(default_factory=TelemetryConfig)
71
+ agent: AgentConfig = Field(default_factory=AgentConfig)
51
72
 
52
73
  @staticmethod
53
74
  def _deep_merge(base: Dict[str, Any], update: Dict[str, Any]) -> Dict[str, Any]:
@@ -73,7 +94,11 @@ class MonocoConfig(BaseModel):
73
94
  # Determine project path
74
95
  cwd = Path(project_root) if project_root else Path.cwd()
75
96
  proj_path_hidden = cwd / ".monoco" / "config.yaml"
76
- proj_path_root = cwd / "monoco.yaml"
97
+
98
+ # [Legacy] Check for monoco.yaml and warn
99
+ proj_path_legacy = cwd / "monoco.yaml"
100
+ if proj_path_legacy.exists():
101
+ logger.warning(f"Legacy configuration found: {proj_path_legacy}. Please move it to .monoco/config.yaml")
77
102
 
78
103
  # 3. Load User Config
79
104
  if home_path.exists():
@@ -86,14 +111,10 @@ class MonocoConfig(BaseModel):
86
111
  # We don't want to crash on config load fail, implementing simple warning equivalent
87
112
  pass
88
113
 
89
- # 4. Load Project Config (prefer .monoco/config.yaml, fallback to monoco.yaml)
90
- target_proj_conf = proj_path_hidden if proj_path_hidden.exists() else (
91
- proj_path_root if proj_path_root.exists() else None
92
- )
93
-
94
- if target_proj_conf:
114
+ # 4. Load Project Config (Only .monoco/config.yaml)
115
+ if proj_path_hidden.exists():
95
116
  try:
96
- with open(target_proj_conf, "r") as f:
117
+ with open(proj_path_hidden, "r") as f:
97
118
  proj_config = yaml.safe_load(f)
98
119
  if proj_config:
99
120
  cls._deep_merge(config_data, proj_config)
@@ -111,3 +132,34 @@ def get_config(project_root: Optional[str] = None) -> MonocoConfig:
111
132
  if _settings is None or project_root is not None:
112
133
  _settings = MonocoConfig.load(project_root)
113
134
  return _settings
135
+
136
+ class ConfigScope(str, Enum):
137
+ GLOBAL = "global"
138
+ PROJECT = "project"
139
+
140
+ def get_config_path(scope: ConfigScope, project_root: Optional[str] = None) -> Path:
141
+ """Get the path to the configuration file for a given scope."""
142
+ if scope == ConfigScope.GLOBAL:
143
+ return Path.home() / ".monoco" / "config.yaml"
144
+ else:
145
+ cwd = Path(project_root) if project_root else Path.cwd()
146
+ return cwd / ".monoco" / "config.yaml"
147
+
148
+ def load_raw_config(scope: ConfigScope, project_root: Optional[str] = None) -> Dict[str, Any]:
149
+ """Load raw configuration dictionary from a specific scope."""
150
+ path = get_config_path(scope, project_root)
151
+ if not path.exists():
152
+ return {}
153
+ try:
154
+ with open(path, "r") as f:
155
+ return yaml.safe_load(f) or {}
156
+ except Exception as e:
157
+ logger.warning(f"Failed to load config from {path}: {e}")
158
+ return {}
159
+
160
+ def save_raw_config(scope: ConfigScope, data: Dict[str, Any], project_root: Optional[str] = None) -> None:
161
+ """Save raw configuration dictionary to a specific scope."""
162
+ path = get_config_path(scope, project_root)
163
+ path.parent.mkdir(parents=True, exist_ok=True)
164
+ with open(path, "w") as f:
165
+ yaml.dump(data, f, default_flow_style=False)
monoco/core/feature.py ADDED
@@ -0,0 +1,58 @@
1
+ from abc import ABC, abstractmethod
2
+ from dataclasses import dataclass, field
3
+ from pathlib import Path
4
+ from typing import Dict, List, Optional
5
+
6
+ @dataclass
7
+ class IntegrationData:
8
+ """
9
+ Data collection returned by a feature for integration into the Agent environment.
10
+ """
11
+ # System Prompts to be injected into agent configuration (e.g., .cursorrules)
12
+ # Key: Section Title (e.g., "Issue Management"), Value: Markdown Content
13
+ system_prompts: Dict[str, str] = field(default_factory=dict)
14
+
15
+ # Paths to skill directories or files to be copied/symlinked
16
+ # DEPRECATED: Skill distribution is cancelled. Only prompts are synced.
17
+ skills: List[Path] = field(default_factory=list)
18
+
19
+ class MonocoFeature(ABC):
20
+ """
21
+ Abstract base class for all Monoco features.
22
+ Features must implement this protocol to participate in init and sync lifecycles.
23
+ """
24
+
25
+ @property
26
+ @abstractmethod
27
+ def name(self) -> str:
28
+ """Unique identifier for the feature (e.g., 'issue', 'spike')."""
29
+ pass
30
+
31
+ @abstractmethod
32
+ def initialize(self, root: Path, config: Dict) -> None:
33
+ """
34
+ Lifecycle hook: Physical Structure Initialization.
35
+ Called during `monoco init`.
36
+ Responsible for creating necessary directories, files, and config templates.
37
+
38
+ Args:
39
+ root: The root directory of the project.
40
+ config: The full project configuration dictionary.
41
+ """
42
+ pass
43
+
44
+ @abstractmethod
45
+ def integrate(self, root: Path, config: Dict) -> IntegrationData:
46
+ """
47
+ Lifecycle hook: Agent Environment Integration.
48
+ Called during `monoco sync`.
49
+ Responsible for returning data (prompts, skills) needed for the Agent Setup.
50
+
51
+ Args:
52
+ root: The root directory of the project.
53
+ config: The full project configuration dictionary.
54
+
55
+ Returns:
56
+ IntegrationData object containing prompts and skills.
57
+ """
58
+ pass
@@ -0,0 +1,196 @@
1
+ import re
2
+ from pathlib import Path
3
+ from typing import Dict, List, Optional
4
+
5
+ class PromptInjector:
6
+ """
7
+ Engine for injecting managed content into Markdown-like files (e.g., .cursorrules, GEMINI.md).
8
+ Maintains a 'Managed Block' defined by a specific header.
9
+ """
10
+
11
+ MANAGED_HEADER = "## Monoco Toolkit"
12
+
13
+ def __init__(self, target_file: Path):
14
+ self.target_file = target_file
15
+
16
+ def inject(self, prompts: Dict[str, str]) -> bool:
17
+ """
18
+ Injects the provided prompts into the target file.
19
+
20
+ Args:
21
+ prompts: A dictionary where key is the section title and value is the content.
22
+
23
+ Returns:
24
+ True if changes were written, False otherwise.
25
+ """
26
+ current_content = ""
27
+ if self.target_file.exists():
28
+ current_content = self.target_file.read_text(encoding="utf-8")
29
+
30
+ new_content = self._merge_content(current_content, prompts)
31
+
32
+ if new_content != current_content:
33
+ self.target_file.write_text(new_content, encoding="utf-8")
34
+ return True
35
+ return False
36
+
37
+ def _merge_content(self, original: str, prompts: Dict[str, str]) -> str:
38
+ """
39
+ Merges the generated prompts into the original content within the managed block.
40
+ """
41
+ # 1. Generate the new managed block content
42
+ managed_block = [self.MANAGED_HEADER, ""]
43
+ managed_block.append("> **Auto-Generated**: This section is managed by Monoco. Do not edit manually.\n")
44
+
45
+ for title, content in prompts.items():
46
+ managed_block.append(f"### {title}")
47
+ managed_block.append("") # Blank line after header
48
+
49
+ # Sanitize content: remove leading header if it matches the title
50
+ clean_content = content.strip()
51
+ # Regex to match optional leading hash header matching the title (case insensitive)
52
+ # e.g. "### Issue Management" or "# Issue Management"
53
+ pattern = r"^(#+\s*)" + re.escape(title) + r"\s*\n"
54
+ match = re.match(pattern, clean_content, re.IGNORECASE)
55
+
56
+ if match:
57
+ clean_content = clean_content[match.end():].strip()
58
+
59
+ managed_block.append(clean_content)
60
+ managed_block.append("") # Blank line after section
61
+
62
+ managed_block_str = "\n".join(managed_block).strip() + "\n"
63
+
64
+ # 2. Find and replace/append in the original content
65
+ lines = original.splitlines()
66
+ start_idx = -1
67
+ end_idx = -1
68
+
69
+ # Find start
70
+ for i, line in enumerate(lines):
71
+ if line.strip() == self.MANAGED_HEADER:
72
+ start_idx = i
73
+ break
74
+
75
+ if start_idx == -1:
76
+ # Block not found, append to end
77
+ if original and not original.endswith("\n"):
78
+ return original + "\n\n" + managed_block_str
79
+ elif original:
80
+ return original + "\n" + managed_block_str
81
+ else:
82
+ return managed_block_str
83
+
84
+ # Find end: Look for next header of level 1 (assuming Managed Header is H1)
85
+ # Or EOF
86
+ # Note: If MANAGED_HEADER is "# ...", we look for next "# ..."
87
+ # But allow "## ..." as children.
88
+
89
+ header_level_match = re.match(r"^(#+)\s", self.MANAGED_HEADER)
90
+ header_level_prefix = header_level_match.group(1) if header_level_match else "#"
91
+
92
+ for i in range(start_idx + 1, len(lines)):
93
+ line = lines[i]
94
+ # Check if this line is a header of the same level or higher (fewer #s)
95
+ # e.g. if Managed is "###", then "#" and "##" are higher/parents, "###" is sibling.
96
+ # We treat siblings as end of block too.
97
+ if line.startswith("#"):
98
+ # Match regex to get level
99
+ match = re.match(r"^(#+)\s", line)
100
+ if match:
101
+ level = match.group(1)
102
+ if len(level) <= len(header_level_prefix):
103
+ end_idx = i
104
+ break
105
+
106
+ if end_idx == -1:
107
+ end_idx = len(lines)
108
+
109
+ # 3. Construct result
110
+ pre_block = "\n".join(lines[:start_idx])
111
+ post_block = "\n".join(lines[end_idx:])
112
+
113
+ result = pre_block
114
+ if result:
115
+ result += "\n\n"
116
+
117
+ result += managed_block_str
118
+
119
+ if post_block:
120
+ # Ensure separation if post block exists and isn't just empty lines
121
+ if post_block.strip():
122
+ result += "\n" + post_block
123
+ else:
124
+ result += post_block # Keep trailing newlines if any, or normalize?
125
+
126
+ return result.strip() + "\n"
127
+
128
+ def remove(self) -> bool:
129
+ """
130
+ Removes the managed block from the target file.
131
+
132
+ Returns:
133
+ True if changes were written (block removed), False otherwise.
134
+ """
135
+ if not self.target_file.exists():
136
+ return False
137
+
138
+ current_content = self.target_file.read_text(encoding="utf-8")
139
+ lines = current_content.splitlines()
140
+
141
+ start_idx = -1
142
+ end_idx = -1
143
+
144
+ # Find start
145
+ for i, line in enumerate(lines):
146
+ if line.strip() == self.MANAGED_HEADER:
147
+ start_idx = i
148
+ break
149
+
150
+ if start_idx == -1:
151
+ return False
152
+
153
+ # Find end: exact logic as in _merge_content
154
+ header_level_match = re.match(r"^(#+)\s", self.MANAGED_HEADER)
155
+ header_level_prefix = header_level_match.group(1) if header_level_match else "#"
156
+
157
+ for i in range(start_idx + 1, len(lines)):
158
+ line = lines[i]
159
+ if line.startswith("#"):
160
+ match = re.match(r"^(#+)\s", line)
161
+ if match:
162
+ level = match.group(1)
163
+ if len(level) <= len(header_level_prefix):
164
+ end_idx = i
165
+ break
166
+
167
+ if end_idx == -1:
168
+ end_idx = len(lines)
169
+
170
+ # Reconstruct content without the block
171
+ # We also need to be careful about surrounding newlines to avoid leaving gaps
172
+
173
+ # Check lines before start_idx
174
+ while start_idx > 0 and not lines[start_idx-1].strip():
175
+ start_idx -= 1
176
+
177
+ # Check lines after end_idx (optional, but good for cleanup)
178
+ # Usually end_idx points to the next header or EOF.
179
+ # If it points to next header, we keep it.
180
+
181
+ pre_block = lines[:start_idx]
182
+ post_block = lines[end_idx:]
183
+
184
+ # If we removed everything, the file might become empty or just newlines
185
+
186
+ new_lines = pre_block + post_block
187
+ if not new_lines:
188
+ new_content = ""
189
+ else:
190
+ new_content = "\n".join(new_lines).strip() + "\n"
191
+
192
+ if new_content != current_content:
193
+ self.target_file.write_text(new_content, encoding="utf-8")
194
+ return True
195
+
196
+ return False
@@ -0,0 +1,181 @@
1
+ """
2
+ Core Integration Registry for Agent Frameworks.
3
+
4
+ This module provides a centralized registry for managing integrations with various
5
+ agent frameworks (Cursor, Claude, Gemini, Qwen, Antigravity, etc.).
6
+
7
+ It defines the standard structure for framework integrations and provides utilities
8
+ for detection and configuration.
9
+ """
10
+
11
+ from pathlib import Path
12
+ from typing import Dict, List, Optional
13
+ from pydantic import BaseModel, Field
14
+
15
+
16
+ class AgentIntegration(BaseModel):
17
+ """
18
+ Configuration for a single agent framework integration.
19
+
20
+ Attributes:
21
+ key: Unique identifier for the framework (e.g., 'cursor', 'gemini')
22
+ name: Human-readable name of the framework
23
+ system_prompt_file: Path to the system prompt/rules file relative to project root
24
+ skill_root_dir: Path to the skills directory relative to project root
25
+ enabled: Whether this integration is active (default: True)
26
+ """
27
+ key: str = Field(..., description="Unique framework identifier")
28
+ name: str = Field(..., description="Human-readable framework name")
29
+ system_prompt_file: str = Field(..., description="Path to system prompt file (relative to project root)")
30
+ skill_root_dir: str = Field(..., description="Path to skills directory (relative to project root)")
31
+ enabled: bool = Field(default=True, description="Whether this integration is active")
32
+
33
+
34
+ # Default Integration Registry
35
+ DEFAULT_INTEGRATIONS: Dict[str, AgentIntegration] = {
36
+ "cursor": AgentIntegration(
37
+ key="cursor",
38
+ name="Cursor",
39
+ system_prompt_file=".cursorrules",
40
+ skill_root_dir=".cursor/skills/",
41
+ ),
42
+ "claude": AgentIntegration(
43
+ key="claude",
44
+ name="Claude Code",
45
+ system_prompt_file="CLAUDE.md",
46
+ skill_root_dir=".claude/skills/",
47
+ ),
48
+ "gemini": AgentIntegration(
49
+ key="gemini",
50
+ name="Gemini CLI",
51
+ system_prompt_file="GEMINI.md",
52
+ skill_root_dir=".gemini/skills/",
53
+ ),
54
+ "qwen": AgentIntegration(
55
+ key="qwen",
56
+ name="Qwen Code",
57
+ system_prompt_file="QWEN.md",
58
+ skill_root_dir=".qwen/skills/",
59
+ ),
60
+ "agent": AgentIntegration(
61
+ key="agent",
62
+ name="Antigravity",
63
+ system_prompt_file="GEMINI.md",
64
+ skill_root_dir=".agent/skills/",
65
+ ),
66
+ }
67
+
68
+
69
+ def get_integration(
70
+ name: str,
71
+ config_overrides: Optional[Dict[str, AgentIntegration]] = None
72
+ ) -> Optional[AgentIntegration]:
73
+ """
74
+ Get an agent integration by name.
75
+
76
+ Args:
77
+ name: The framework key (e.g., 'cursor', 'gemini')
78
+ config_overrides: Optional user-defined integrations from config
79
+
80
+ Returns:
81
+ AgentIntegration if found, None otherwise
82
+
83
+ Priority:
84
+ 1. User config overrides
85
+ 2. Default registry
86
+ """
87
+ # Check user overrides first
88
+ if config_overrides and name in config_overrides:
89
+ return config_overrides[name]
90
+
91
+ # Fall back to defaults
92
+ return DEFAULT_INTEGRATIONS.get(name)
93
+
94
+
95
+ def get_all_integrations(
96
+ config_overrides: Optional[Dict[str, AgentIntegration]] = None,
97
+ enabled_only: bool = True
98
+ ) -> Dict[str, AgentIntegration]:
99
+ """
100
+ Get all available integrations.
101
+
102
+ Args:
103
+ config_overrides: Optional user-defined integrations from config
104
+ enabled_only: If True, only return enabled integrations
105
+
106
+ Returns:
107
+ Dictionary of all integrations (merged defaults + overrides)
108
+ """
109
+ # Start with defaults
110
+ all_integrations = DEFAULT_INTEGRATIONS.copy()
111
+
112
+ # Merge user overrides
113
+ if config_overrides:
114
+ all_integrations.update(config_overrides)
115
+
116
+ # Filter by enabled status if requested
117
+ if enabled_only:
118
+ return {k: v for k, v in all_integrations.items() if v.enabled}
119
+
120
+ return all_integrations
121
+
122
+
123
+ def detect_frameworks(root: Path) -> List[str]:
124
+ """
125
+ Auto-detect which agent frameworks are present in the project.
126
+
127
+ Detection is based on the existence of characteristic files/directories
128
+ for each framework.
129
+
130
+ Args:
131
+ root: Project root directory
132
+
133
+ Returns:
134
+ List of detected framework keys (e.g., ['cursor', 'gemini'])
135
+
136
+ Example:
137
+ >>> root = Path("/path/to/project")
138
+ >>> frameworks = detect_frameworks(root)
139
+ >>> print(frameworks)
140
+ ['cursor', 'gemini']
141
+ """
142
+ detected = []
143
+
144
+ for key, integration in DEFAULT_INTEGRATIONS.items():
145
+ # Check if system prompt file exists
146
+ prompt_file = root / integration.system_prompt_file
147
+
148
+ # Check if skill directory exists
149
+ skill_dir = root / integration.skill_root_dir
150
+
151
+ # Consider framework present if either exists
152
+ if prompt_file.exists() or skill_dir.exists():
153
+ detected.append(key)
154
+
155
+ return detected
156
+
157
+
158
+ def get_active_integrations(
159
+ root: Path,
160
+ config_overrides: Optional[Dict[str, AgentIntegration]] = None,
161
+ auto_detect: bool = True
162
+ ) -> Dict[str, AgentIntegration]:
163
+ """
164
+ Get integrations that are both enabled and detected in the project.
165
+
166
+ Args:
167
+ root: Project root directory
168
+ config_overrides: Optional user-defined integrations from config
169
+ auto_detect: If True, only return integrations detected in the project
170
+
171
+ Returns:
172
+ Dictionary of active integrations
173
+ """
174
+ all_integrations = get_all_integrations(config_overrides, enabled_only=True)
175
+
176
+ if not auto_detect:
177
+ return all_integrations
178
+
179
+ # Filter by detection
180
+ detected_keys = detect_frameworks(root)
181
+ return {k: v for k, v in all_integrations.items() if k in detected_keys}
@@ -0,0 +1,36 @@
1
+ from typing import Dict, List, Type
2
+ from monoco.core.feature import MonocoFeature
3
+
4
+ class FeatureRegistry:
5
+ _features: Dict[str, MonocoFeature] = {}
6
+
7
+ @classmethod
8
+ def register(cls, feature: MonocoFeature):
9
+ """Register a feature instance."""
10
+ cls._features[feature.name] = feature
11
+
12
+ @classmethod
13
+ def get_features(cls) -> List[MonocoFeature]:
14
+ """Get all registered features."""
15
+ return list(cls._features.values())
16
+
17
+ @classmethod
18
+ def get_feature(cls, name: str) -> MonocoFeature:
19
+ """Get a specific feature by name."""
20
+ return cls._features.get(name)
21
+
22
+ @classmethod
23
+ def load_defaults(cls):
24
+ """
25
+ Load default core features.
26
+ TODO: In the future, this could be dynamic via entry points.
27
+ """
28
+ # Import here to avoid circular dependencies at module level
29
+ from monoco.features.issue.adapter import IssueFeature
30
+ from monoco.features.spike.adapter import SpikeFeature
31
+ from monoco.features.i18n.adapter import I18nFeature
32
+
33
+ cls.register(IssueFeature())
34
+ cls.register(SpikeFeature())
35
+ cls.register(I18nFeature())
36
+ pass
@@ -0,0 +1,8 @@
1
+ ### Monoco Core
2
+
3
+ Core toolkit commands for project management.
4
+
5
+ - **Init**: `monoco init` (Initialize new Monoco project)
6
+ - **Config**: `monoco config get|set <key> [value]` (Manage configuration)
7
+ - **Sync**: `monoco sync` (Synchronize with agent environments)
8
+ - **Uninstall**: `monoco uninstall` (Clean up agent integrations)
@@ -0,0 +1,66 @@
1
+ ---
2
+ name: monoco-core
3
+ description: Core skill for Monoco Toolkit. Provides essential commands for project initialization, configuration, and workspace management.
4
+ ---
5
+
6
+ # Monoco Core
7
+
8
+ Core functionality and commands for the Monoco Toolkit.
9
+
10
+ ## Overview
11
+
12
+ Monoco is a developer productivity toolkit that provides:
13
+
14
+ - **Project initialization** with standardized structure
15
+ - **Configuration management** at global and project levels
16
+ - **Workspace management** for multi-project setups
17
+
18
+ ## Key Commands
19
+
20
+ ### Project Setup
21
+
22
+ - **`monoco init`**: Initialize a new Monoco project
23
+ - Creates `.monoco/` directory with default configuration
24
+ - Sets up project structure (Issues/, .references/, etc.)
25
+ - Generates initial documentation
26
+
27
+ ### Configuration
28
+
29
+ - **`monoco config`**: Manage configuration
30
+ - `monoco config get <key>`: View configuration value
31
+ - `monoco config set <key> <value>`: Update configuration
32
+ - Supports both global (`~/.monoco/config.yaml`) and project (`.monoco/config.yaml`) scopes
33
+
34
+ ### Agent Integration
35
+
36
+ - **`monoco sync`**: Synchronize with agent environments
37
+
38
+ - Injects system prompts into agent configuration files (GEMINI.md, CLAUDE.md, etc.)
39
+ - Distributes skills to agent framework directories
40
+ - Respects language configuration from `i18n.source_lang`
41
+
42
+ - **`monoco uninstall`**: Clean up agent integrations
43
+ - Removes managed blocks from agent configuration files
44
+ - Cleans up distributed skills
45
+
46
+ ## Configuration Structure
47
+
48
+ Configuration is stored in YAML format at:
49
+
50
+ - **Global**: `~/.monoco/config.yaml`
51
+ - **Project**: `.monoco/config.yaml`
52
+
53
+ Key configuration sections:
54
+
55
+ - `core`: Editor, log level, author
56
+ - `paths`: Directory paths (issues, spikes, specs)
57
+ - `project`: Project metadata, spike repos, workspace members
58
+ - `i18n`: Internationalization settings
59
+ - `agent`: Agent framework integration settings
60
+
61
+ ## Best Practices
62
+
63
+ 1. **Use CLI commands** instead of manual file editing when possible
64
+ 2. **Run `monoco sync`** after configuration changes to update agent environments
65
+ 3. **Commit `.monoco/config.yaml`** to version control for team consistency
66
+ 4. **Keep global config minimal** - most settings should be project-specific
@@ -0,0 +1,8 @@
1
+ ### Monoco 核心
2
+
3
+ 项目管理的核心工具包命令。
4
+
5
+ - **初始化**: `monoco init` (初始化新的 Monoco 项目)
6
+ - **配置**: `monoco config get|set <key> [value]` (管理配置)
7
+ - **同步**: `monoco sync` (与 agent 环境同步)
8
+ - **卸载**: `monoco uninstall` (清理 agent 集成)