monoco-toolkit 0.3.6__py3-none-any.whl → 0.3.10__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 (113) hide show
  1. monoco/cli/workspace.py +1 -1
  2. monoco/core/config.py +58 -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/injection.py +63 -29
  11. monoco/core/integrations.py +8 -2
  12. monoco/core/output.py +5 -5
  13. monoco/core/registry.py +9 -1
  14. monoco/core/resource/__init__.py +5 -0
  15. monoco/core/resource/finder.py +98 -0
  16. monoco/core/resource/manager.py +91 -0
  17. monoco/core/resource/models.py +35 -0
  18. monoco/core/resources/en/{SKILL.md → skills/monoco_core/SKILL.md} +2 -0
  19. monoco/core/resources/zh/{SKILL.md → skills/monoco_core/SKILL.md} +2 -0
  20. monoco/core/setup.py +1 -1
  21. monoco/core/skill_framework.py +292 -0
  22. monoco/core/skills.py +538 -254
  23. monoco/core/sync.py +73 -1
  24. monoco/core/workflow_converter.py +420 -0
  25. monoco/features/{scheduler → agent}/__init__.py +5 -3
  26. monoco/features/agent/adapter.py +31 -0
  27. monoco/features/agent/apoptosis.py +44 -0
  28. monoco/features/agent/cli.py +296 -0
  29. monoco/features/agent/config.py +96 -0
  30. monoco/features/agent/defaults.py +12 -0
  31. monoco/features/{scheduler → agent}/engines.py +32 -6
  32. monoco/features/agent/flow_skills.py +281 -0
  33. monoco/features/agent/manager.py +91 -0
  34. monoco/features/{scheduler → agent}/models.py +6 -3
  35. monoco/features/agent/resources/atoms/atom-code-dev.yaml +61 -0
  36. monoco/features/agent/resources/atoms/atom-issue-lifecycle.yaml +73 -0
  37. monoco/features/agent/resources/atoms/atom-knowledge.yaml +55 -0
  38. monoco/features/agent/resources/atoms/atom-review.yaml +60 -0
  39. monoco/features/agent/resources/en/skills/flow_engineer/SKILL.md +94 -0
  40. monoco/features/agent/resources/en/skills/flow_manager/SKILL.md +93 -0
  41. monoco/features/agent/resources/en/skills/flow_planner/SKILL.md +85 -0
  42. monoco/features/agent/resources/en/skills/flow_reviewer/SKILL.md +114 -0
  43. monoco/features/agent/resources/roles/role-engineer.yaml +49 -0
  44. monoco/features/agent/resources/roles/role-manager.yaml +46 -0
  45. monoco/features/agent/resources/roles/role-planner.yaml +46 -0
  46. monoco/features/agent/resources/roles/role-reviewer.yaml +47 -0
  47. monoco/features/agent/resources/workflows/workflow-dev.yaml +83 -0
  48. monoco/features/agent/resources/workflows/workflow-issue-create.yaml +72 -0
  49. monoco/features/agent/resources/workflows/workflow-review.yaml +94 -0
  50. monoco/features/agent/resources/zh/skills/flow_engineer/SKILL.md +94 -0
  51. monoco/features/agent/resources/zh/skills/flow_manager/SKILL.md +88 -0
  52. monoco/features/agent/resources/zh/skills/flow_planner/SKILL.md +259 -0
  53. monoco/features/agent/resources/zh/skills/flow_reviewer/SKILL.md +137 -0
  54. monoco/features/{scheduler → agent}/session.py +36 -1
  55. monoco/features/{scheduler → agent}/worker.py +40 -4
  56. monoco/features/glossary/adapter.py +31 -0
  57. monoco/features/glossary/config.py +5 -0
  58. monoco/features/glossary/resources/en/AGENTS.md +29 -0
  59. monoco/features/glossary/resources/en/skills/monoco_glossary/SKILL.md +35 -0
  60. monoco/features/glossary/resources/zh/AGENTS.md +29 -0
  61. monoco/features/glossary/resources/zh/skills/monoco_glossary/SKILL.md +35 -0
  62. monoco/features/i18n/resources/en/skills/i18n_scan_workflow/SKILL.md +105 -0
  63. monoco/features/i18n/resources/en/{SKILL.md → skills/monoco_i18n/SKILL.md} +2 -0
  64. monoco/features/i18n/resources/zh/skills/i18n_scan_workflow/SKILL.md +105 -0
  65. monoco/features/i18n/resources/zh/{SKILL.md → skills/monoco_i18n/SKILL.md} +2 -0
  66. monoco/features/issue/commands.py +427 -21
  67. monoco/features/issue/core.py +140 -1
  68. monoco/features/issue/criticality.py +553 -0
  69. monoco/features/issue/domain/models.py +28 -2
  70. monoco/features/issue/engine/machine.py +75 -15
  71. monoco/features/issue/git_service.py +185 -0
  72. monoco/features/issue/linter.py +291 -62
  73. monoco/features/issue/models.py +50 -2
  74. monoco/features/issue/resources/en/skills/issue_create_workflow/SKILL.md +167 -0
  75. monoco/features/issue/resources/en/skills/issue_develop_workflow/SKILL.md +224 -0
  76. monoco/features/issue/resources/en/skills/issue_lifecycle_workflow/SKILL.md +159 -0
  77. monoco/features/issue/resources/en/skills/issue_refine_workflow/SKILL.md +203 -0
  78. monoco/features/issue/resources/en/{SKILL.md → skills/monoco_issue/SKILL.md} +50 -0
  79. monoco/features/issue/resources/zh/skills/issue_create_workflow/SKILL.md +167 -0
  80. monoco/features/issue/resources/zh/skills/issue_develop_workflow/SKILL.md +224 -0
  81. monoco/features/issue/resources/zh/skills/issue_lifecycle_workflow/SKILL.md +159 -0
  82. monoco/features/issue/resources/zh/skills/issue_refine_workflow/SKILL.md +203 -0
  83. monoco/features/issue/resources/zh/{SKILL.md → skills/monoco_issue/SKILL.md} +52 -0
  84. monoco/features/issue/validator.py +185 -65
  85. monoco/features/memo/__init__.py +2 -1
  86. monoco/features/memo/adapter.py +32 -0
  87. monoco/features/memo/cli.py +36 -14
  88. monoco/features/memo/core.py +59 -0
  89. monoco/features/memo/resources/en/skills/monoco_memo/SKILL.md +77 -0
  90. monoco/features/memo/resources/en/skills/note_processing_workflow/SKILL.md +140 -0
  91. monoco/features/memo/resources/zh/AGENTS.md +8 -0
  92. monoco/features/memo/resources/zh/skills/monoco_memo/SKILL.md +77 -0
  93. monoco/features/memo/resources/zh/skills/note_processing_workflow/SKILL.md +140 -0
  94. monoco/features/spike/resources/en/{SKILL.md → skills/monoco_spike/SKILL.md} +2 -0
  95. monoco/features/spike/resources/en/skills/research_workflow/SKILL.md +121 -0
  96. monoco/features/spike/resources/zh/{SKILL.md → skills/monoco_spike/SKILL.md} +2 -0
  97. monoco/features/spike/resources/zh/skills/research_workflow/SKILL.md +121 -0
  98. monoco/main.py +2 -3
  99. monoco_toolkit-0.3.10.dist-info/METADATA +124 -0
  100. monoco_toolkit-0.3.10.dist-info/RECORD +156 -0
  101. monoco/features/scheduler/cli.py +0 -285
  102. monoco/features/scheduler/config.py +0 -68
  103. monoco/features/scheduler/defaults.py +0 -54
  104. monoco/features/scheduler/manager.py +0 -49
  105. monoco/features/scheduler/reliability.py +0 -106
  106. monoco/features/skills/core.py +0 -102
  107. monoco_toolkit-0.3.6.dist-info/METADATA +0 -127
  108. monoco_toolkit-0.3.6.dist-info/RECORD +0 -97
  109. /monoco/core/{hooks.py → githooks.py} +0 -0
  110. /monoco/features/{skills → glossary}/__init__.py +0 -0
  111. {monoco_toolkit-0.3.6.dist-info → monoco_toolkit-0.3.10.dist-info}/WHEEL +0 -0
  112. {monoco_toolkit-0.3.6.dist-info → monoco_toolkit-0.3.10.dist-info}/entry_points.txt +0 -0
  113. {monoco_toolkit-0.3.6.dist-info → monoco_toolkit-0.3.10.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,91 @@
1
+ from typing import List, Optional, Dict
2
+ from pathlib import Path
3
+ import shutil
4
+ import os
5
+
6
+ from .models import ResourceNode, ResourceType
7
+ from .finder import ResourceFinder
8
+
9
+ class ResourceManager:
10
+ def __init__(self, source_lang: str = "en"):
11
+ self.finder = ResourceFinder()
12
+ self.source_lang = source_lang
13
+
14
+ def list_resources(self, package: str, type: Optional[ResourceType] = None, lang: Optional[str] = None) -> List[ResourceNode]:
15
+ """
16
+ Low-level listing of resources with optional exact filtering.
17
+ """
18
+ all_nodes = self.finder.scan_package(package)
19
+ filtered = []
20
+ for node in all_nodes:
21
+ if type and node.type != type:
22
+ continue
23
+ if lang and node.language != lang:
24
+ continue
25
+ filtered.append(node)
26
+ return filtered
27
+
28
+ def get_merged_resources(self, package: str, type: ResourceType, target_lang: str) -> List[ResourceNode]:
29
+ """
30
+ Get resources of a specific type, merging source language defaults with target language overrides.
31
+ Returns a list of unique resources (by name), prioritizing target_lang.
32
+ """
33
+ all_nodes = self.finder.scan_package(package)
34
+ type_nodes = [n for n in all_nodes if n.type == type]
35
+
36
+ # Dictionary to hold the best match for each filename: name -> ResourceNode
37
+ best_matches: Dict[str, ResourceNode] = {}
38
+
39
+ # 1. Populate with source language (Default Base)
40
+ for node in type_nodes:
41
+ if node.language == self.source_lang:
42
+ best_matches[node.name] = node
43
+
44
+ # 2. Override with target language if different
45
+ if target_lang != self.source_lang:
46
+ for node in type_nodes:
47
+ if node.language == target_lang:
48
+ best_matches[node.name] = node
49
+
50
+ return list(best_matches.values())
51
+
52
+ def extract_to(self, nodes: List[ResourceNode], destination: Path, symlink: bool = False, force: bool = True) -> int:
53
+ """
54
+ Extracts (copy or symlink) resources to the destination directory.
55
+ Returns count of extracted items.
56
+
57
+ Args:
58
+ nodes: List of resources to extract
59
+ destination: Target directory
60
+ symlink: If True, create symbolic links instead of copying
61
+ force: If True, overwrite existing files/symlinks
62
+ """
63
+ destination.mkdir(parents=True, exist_ok=True)
64
+ count = 0
65
+
66
+ for node in nodes:
67
+ dest_path = destination / node.name
68
+
69
+ if dest_path.exists():
70
+ if not force:
71
+ continue
72
+ # Remove existing
73
+ if dest_path.is_symlink() or dest_path.is_file():
74
+ dest_path.unlink()
75
+ elif dest_path.is_dir():
76
+ shutil.rmtree(dest_path)
77
+
78
+ try:
79
+ if symlink:
80
+ # Symlink target must be absolute
81
+ dest_path.symlink_to(node.path)
82
+ else:
83
+ if node.path.is_dir():
84
+ shutil.copytree(node.path, dest_path)
85
+ else:
86
+ shutil.copy2(node.path, dest_path)
87
+ count += 1
88
+ except Exception as e:
89
+ print(f"Error extracting {node.name}: {e}")
90
+
91
+ return count
@@ -0,0 +1,35 @@
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+ from typing import Optional, List
4
+ from enum import Enum
5
+
6
+ class ResourceType(str, Enum):
7
+ PROMPTS = "prompts"
8
+ RULES = "rules"
9
+ SKILLS = "skills"
10
+ ROLES = "roles"
11
+ GLOSSARY = "glossary"
12
+ TEMPLATES = "templates"
13
+ DOCS = "docs"
14
+ OTHER = "other"
15
+
16
+ @dataclass
17
+ class ResourceNode:
18
+ """
19
+ Represents a discovered resource file in a Python package.
20
+ """
21
+ name: str
22
+ path: Path # Absolute path to the source file
23
+ type: ResourceType
24
+ language: str # "en", "zh", etc.
25
+ content: Optional[str] = None # Lazy loaded content
26
+
27
+ @property
28
+ def key(self) -> str:
29
+ """Unique identifier for the resource (e.g. 'agent.prompts.system')"""
30
+ return f"{self.type.value}.{self.name}"
31
+
32
+ def read_text(self) -> str:
33
+ if self.content:
34
+ return self.content
35
+ return self.path.read_text(encoding="utf-8")
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: monoco-core
3
3
  description: Core skill for Monoco Toolkit. Provides essential commands for project initialization, configuration, and workspace management.
4
+ type: standard
5
+ version: 1.0.0
4
6
  ---
5
7
 
6
8
  # Monoco Core
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: monoco-core
3
3
  description: Monoco Toolkit 的核心技能。提供项目初始化、配置管理和工作空间管理的基础命令。
4
+ type: standard
5
+ version: 1.0.0
4
6
  ---
5
7
 
6
8
  # Monoco 核心
monoco/core/setup.py CHANGED
@@ -321,7 +321,7 @@ def init_cli(
321
321
 
322
322
  # Initialize Hooks
323
323
  try:
324
- from monoco.core.hooks import install_hooks
324
+ from monoco.core.githooks import install_hooks
325
325
 
326
326
  # Re-load config to get the just-written hooks (or default ones)
327
327
  # Actually we have the dict right here in workspace_config['hooks']
@@ -0,0 +1,292 @@
1
+ """
2
+ Skill Framework for Monoco Toolkit - Three-Level Architecture
3
+
4
+ This module implements the Role-Workflow-Atom three-level skill architecture:
5
+
6
+ 1. Atom Skills (atom): Atomic capabilities that perform single operations
7
+ - atom-issue-lifecycle: Issue lifecycle operations (create, start, submit, close)
8
+ - atom-code-dev: Code development operations (investigate, implement, test, document)
9
+ - atom-knowledge: Knowledge management operations (capture, process, convert, archive)
10
+ - atom-review: Review operations (checkout, verify, challenge, feedback)
11
+
12
+ 2. Workflow Skills (workflow): Orchestration of atom skills into workflows
13
+ - workflow-dev: Development workflow (setup → investigate → implement → test → submit)
14
+ - workflow-issue-create: Issue creation workflow (extract → classify → create)
15
+ - workflow-review: Review workflow (checkout → verify → challenge → decide)
16
+
17
+ 3. Role Skills (role): Configuration layer defining default workflow and preferences
18
+ - role-engineer: Engineer role (uses workflow-dev, autopilot mode)
19
+ - role-manager: Manager role (uses workflow-planning, copilot mode)
20
+ - role-planner: Planner role (uses workflow-design, copilot mode)
21
+ - role-reviewer: Reviewer role (uses workflow-review, autopilot mode)
22
+
23
+ Key Design Principles:
24
+ - Single Responsibility: Each atom skill does one thing
25
+ - Composition over Inheritance: Workflows compose atoms
26
+ - Mode Agnostic: Same workflow supports both copilot and autopilot modes
27
+ - Convention over Configuration: System constraints defined once in atom layer
28
+ """
29
+
30
+ from __future__ import annotations
31
+ from enum import Enum
32
+ from pathlib import Path
33
+ from typing import Any, Dict, List, Optional, Set, Union
34
+ from pydantic import BaseModel, Field, model_validator
35
+ import yaml
36
+
37
+
38
+ class SkillMode(str, Enum):
39
+ """Execution mode for skills."""
40
+ COPILOT = "copilot" # Human-led, AI-assisted
41
+ AUTOPILOT = "autopilot" # AI-led, automatic execution
42
+
43
+
44
+ class SkillType(str, Enum):
45
+ """Skill type in the three-level architecture."""
46
+ ATOM = "atom" # Atomic capability
47
+ WORKFLOW = "workflow" # Workflow orchestration
48
+ ROLE = "role" # Role configuration
49
+
50
+
51
+ class ComplianceRule(BaseModel):
52
+ """A compliance rule."""
53
+ rule: str = Field(..., description="Rule description")
54
+ severity: str = Field(default="warning", description="Rule severity: error, warning, info")
55
+ check: Optional[str] = Field(default=None, description="Check command or condition")
56
+ command: Optional[str] = Field(default=None, description="Associated CLI command")
57
+ mindset: Optional[str] = Field(default=None, description="Related mindset/principle")
58
+ fail_if: Optional[str] = Field(default=None, description="Condition that causes failure")
59
+
60
+
61
+ class AtomOperation(BaseModel):
62
+ """An operation within an atom skill."""
63
+ name: str = Field(..., description="Operation name (e.g., 'create', 'start')")
64
+ description: str = Field(..., description="What this operation does")
65
+ command: Optional[str] = Field(default=None, description="Associated CLI command")
66
+ reminder: Optional[str] = Field(default=None, description="Reminder text for this operation")
67
+ compliance_rules: List[ComplianceRule] = Field(default_factory=list, description="Compliance rules for this operation")
68
+ checkpoints: List[str] = Field(default_factory=list, description="Checkpoints for this operation")
69
+ output: Optional[str] = Field(default=None, description="Expected output")
70
+
71
+
72
+ class Checkpoint(BaseModel):
73
+ """A checkpoint in a workflow stage."""
74
+ description: str = Field(..., description="What to check")
75
+ atom_skill: str = Field(..., description="Atom skill to use")
76
+ operation: str = Field(..., description="Operation to invoke")
77
+ reminder: Optional[str] = Field(default=None, description="Reminder at this checkpoint")
78
+
79
+
80
+ class WorkflowStage(BaseModel):
81
+ """A stage in a workflow."""
82
+ name: str = Field(..., description="Stage name")
83
+ atom_skill: Optional[str] = Field(default=None, description="Atom skill to use (optional for virtual stages)")
84
+ operation: Optional[str] = Field(default=None, description="Operation to invoke (optional for virtual stages)")
85
+ description: Optional[str] = Field(default=None, description="Stage description")
86
+ reminder: Optional[str] = Field(default=None, description="Reminder for this stage")
87
+ checkpoints: List[Checkpoint] = Field(default_factory=list, description="Checkpoints within this stage")
88
+ next_stages: Dict[str, str] = Field(default_factory=dict, description="Conditional next stages")
89
+
90
+
91
+ class ModeConfig(BaseModel):
92
+ """Configuration for a specific execution mode."""
93
+ behavior: str = Field(..., description="Behavior description")
94
+ pause_on: List[str] = Field(default_factory=list, description="Stages to pause on")
95
+ auto_execute: bool = Field(default=False, description="Whether to auto-execute stages")
96
+
97
+
98
+ # ============================================================================
99
+ # Atom Skill Models
100
+ # ============================================================================
101
+
102
+ class AtomSkillMetadata(BaseModel):
103
+ """Metadata for an atom skill."""
104
+ name: str = Field(..., description="Unique atom skill name (e.g., 'atom-issue-lifecycle')")
105
+ type: str = Field(default="atom", description="Skill type (always 'atom')")
106
+ domain: str = Field(..., description="Domain (e.g., 'issue', 'code', 'knowledge', 'review')")
107
+ description: str = Field(..., description="What this atom skill provides")
108
+ version: str = Field(default="1.0.0", description="Skill version")
109
+ author: Optional[str] = Field(default=None, description="Skill author")
110
+
111
+ operations: List[AtomOperation] = Field(..., description="Available operations")
112
+ compliance_rules: List[ComplianceRule] = Field(default_factory=list, description="System-level compliance rules")
113
+
114
+
115
+ # ============================================================================
116
+ # Workflow Skill Models
117
+ # ============================================================================
118
+
119
+ class WorkflowSkillMetadata(BaseModel):
120
+ """Metadata for a workflow skill."""
121
+ name: str = Field(..., description="Unique workflow skill name (e.g., 'workflow-dev')")
122
+ type: str = Field(default="workflow", description="Skill type (always 'workflow')")
123
+ description: str = Field(..., description="What this workflow orchestrates")
124
+ version: str = Field(default="1.0.0", description="Skill version")
125
+ author: Optional[str] = Field(default=None, description="Skill author")
126
+
127
+ dependencies: List[str] = Field(..., description="Required atom skills")
128
+ stages: List[WorkflowStage] = Field(..., description="Workflow stages")
129
+
130
+ mode_config: Dict[SkillMode, ModeConfig] = Field(
131
+ default_factory=dict,
132
+ description="Configuration for copilot and autopilot modes"
133
+ )
134
+
135
+
136
+ # ============================================================================
137
+ # Role Skill Models
138
+ # ============================================================================
139
+
140
+ class RolePreference(BaseModel):
141
+ """A preference for a role."""
142
+ category: str = Field(..., description="Preference category")
143
+ value: str = Field(..., description="Preference value")
144
+
145
+
146
+ class RoleSkillMetadata(BaseModel):
147
+ """Metadata for a role skill."""
148
+ name: str = Field(..., description="Unique role name (e.g., 'role-engineer')")
149
+ type: str = Field(default="role", description="Skill type (always 'role')")
150
+ description: str = Field(..., description="Role description")
151
+ version: str = Field(default="1.0.0", description="Skill version")
152
+ author: Optional[str] = Field(default=None, description="Skill author")
153
+
154
+ workflow: str = Field(..., description="Default workflow skill to use")
155
+ default_mode: SkillMode = Field(default=SkillMode.COPILOT, description="Default execution mode")
156
+
157
+ preferences: List[str] = Field(default_factory=list, description="Role preferences/mindset")
158
+ system_prompt: Optional[str] = Field(default=None, description="System prompt for this role")
159
+ trigger: Optional[str] = Field(default=None, description="When to trigger this role")
160
+ goal: Optional[str] = Field(default=None, description="Goal of this role")
161
+
162
+
163
+ # ============================================================================
164
+ # Unified Skill Loader
165
+ # ============================================================================
166
+
167
+ class SkillLoader:
168
+ """Loader for the three-level skill architecture."""
169
+
170
+ def __init__(self, resources_dir: Path):
171
+ self.resources_dir = resources_dir
172
+ self._atoms: Dict[str, AtomSkillMetadata] = {}
173
+ self._workflows: Dict[str, WorkflowSkillMetadata] = {}
174
+ self._roles: Dict[str, RoleSkillMetadata] = {}
175
+
176
+ def load_all(self) -> None:
177
+ """Load all skills from resources directory."""
178
+ self._load_atoms()
179
+ self._load_workflows()
180
+ self._load_roles()
181
+
182
+ def _load_atoms(self) -> None:
183
+ """Load atom skills from resources/atoms/.
184
+
185
+ Only loads files starting with 'atom-' prefix.
186
+ """
187
+ atoms_dir = self.resources_dir / "atoms"
188
+ if not atoms_dir.exists():
189
+ return
190
+
191
+ for skill_file in atoms_dir.glob("atom-*.yaml"):
192
+ try:
193
+ data = yaml.safe_load(skill_file.read_text())
194
+ atom = AtomSkillMetadata(**data)
195
+ self._atoms[atom.name] = atom
196
+ except Exception as e:
197
+ print(f"Failed to load atom skill {skill_file}: {e}")
198
+
199
+ def _load_workflows(self) -> None:
200
+ """Load workflow skills from resources/workflows/.
201
+
202
+ Only loads files starting with 'workflow-' prefix.
203
+ """
204
+ workflows_dir = self.resources_dir / "workflows"
205
+ if not workflows_dir.exists():
206
+ return
207
+
208
+ for skill_file in workflows_dir.glob("workflow-*.yaml"):
209
+ try:
210
+ data = yaml.safe_load(skill_file.read_text())
211
+ workflow = WorkflowSkillMetadata(**data)
212
+ self._workflows[workflow.name] = workflow
213
+ except Exception as e:
214
+ print(f"Failed to load workflow skill {skill_file}: {e}")
215
+
216
+ def _load_roles(self) -> None:
217
+ """Load role skills from resources/roles/.
218
+
219
+ Only loads files starting with 'role-' prefix to avoid conflicts
220
+ with legacy role definitions.
221
+ """
222
+ roles_dir = self.resources_dir / "roles"
223
+ if not roles_dir.exists():
224
+ return
225
+
226
+ for skill_file in roles_dir.glob("role-*.yaml"):
227
+ try:
228
+ data = yaml.safe_load(skill_file.read_text())
229
+ role = RoleSkillMetadata(**data)
230
+ self._roles[role.name] = role
231
+ except Exception as e:
232
+ print(f"Failed to load role skill {skill_file}: {e}")
233
+
234
+ def get_atom(self, name: str) -> Optional[AtomSkillMetadata]:
235
+ """Get an atom skill by name."""
236
+ return self._atoms.get(name)
237
+
238
+ def get_workflow(self, name: str) -> Optional[WorkflowSkillMetadata]:
239
+ """Get a workflow skill by name."""
240
+ return self._workflows.get(name)
241
+
242
+ def get_role(self, name: str) -> Optional[RoleSkillMetadata]:
243
+ """Get a role skill by name."""
244
+ return self._roles.get(name)
245
+
246
+ def list_atoms(self) -> List[AtomSkillMetadata]:
247
+ """List all atom skills."""
248
+ return list(self._atoms.values())
249
+
250
+ def list_workflows(self) -> List[WorkflowSkillMetadata]:
251
+ """List all workflow skills."""
252
+ return list(self._workflows.values())
253
+
254
+ def list_roles(self) -> List[RoleSkillMetadata]:
255
+ """List all role skills."""
256
+ return list(self._roles.values())
257
+
258
+ def resolve_role_workflow(self, role_name: str) -> Optional[WorkflowSkillMetadata]:
259
+ """Resolve a role to its workflow."""
260
+ role = self.get_role(role_name)
261
+ if role:
262
+ return self.get_workflow(role.workflow)
263
+ return None
264
+
265
+ def validate_workflow(self, workflow_name: str) -> List[str]:
266
+ """Validate a workflow's dependencies are satisfied."""
267
+ errors = []
268
+ workflow = self.get_workflow(workflow_name)
269
+ if not workflow:
270
+ return [f"Workflow '{workflow_name}' not found"]
271
+
272
+ for dep in workflow.dependencies:
273
+ if not self.get_atom(dep):
274
+ errors.append(f"Missing atom skill dependency: {dep}")
275
+
276
+ for stage in workflow.stages:
277
+ # Virtual stages (decision points) don't need atom skills
278
+ if not stage.atom_skill or not stage.operation:
279
+ continue
280
+
281
+ atom = self.get_atom(stage.atom_skill)
282
+ if not atom:
283
+ errors.append(f"Stage '{stage.name}' uses unknown atom skill: {stage.atom_skill}")
284
+ else:
285
+ op_names = [op.name for op in atom.operations]
286
+ if stage.operation not in op_names:
287
+ errors.append(
288
+ f"Stage '{stage.name}' uses unknown operation '{stage.operation}' "
289
+ f"in atom skill '{stage.atom_skill}'"
290
+ )
291
+
292
+ return errors