monoco-toolkit 0.3.9__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 (86) hide show
  1. monoco/core/config.py +7 -0
  2. monoco/core/hooks/builtin/git_cleanup.py +1 -1
  3. monoco/core/injection.py +63 -29
  4. monoco/core/integrations.py +2 -2
  5. monoco/core/output.py +5 -5
  6. monoco/core/registry.py +7 -1
  7. monoco/core/resource/__init__.py +5 -0
  8. monoco/core/resource/finder.py +98 -0
  9. monoco/core/resource/manager.py +91 -0
  10. monoco/core/resource/models.py +35 -0
  11. monoco/core/resources/en/{SKILL.md → skills/monoco_core/SKILL.md} +2 -0
  12. monoco/core/resources/zh/{SKILL.md → skills/monoco_core/SKILL.md} +2 -0
  13. monoco/core/skill_framework.py +292 -0
  14. monoco/core/skills.py +471 -371
  15. monoco/core/sync.py +73 -1
  16. monoco/core/workflow_converter.py +420 -0
  17. monoco/features/agent/__init__.py +2 -2
  18. monoco/features/agent/adapter.py +31 -0
  19. monoco/features/agent/apoptosis.py +44 -0
  20. monoco/features/agent/cli.py +101 -144
  21. monoco/features/agent/config.py +35 -21
  22. monoco/features/agent/defaults.py +6 -49
  23. monoco/features/agent/engines.py +32 -6
  24. monoco/features/agent/manager.py +6 -1
  25. monoco/features/agent/models.py +2 -2
  26. monoco/features/agent/resources/atoms/atom-code-dev.yaml +61 -0
  27. monoco/features/agent/resources/atoms/atom-issue-lifecycle.yaml +73 -0
  28. monoco/features/agent/resources/atoms/atom-knowledge.yaml +55 -0
  29. monoco/features/agent/resources/atoms/atom-review.yaml +60 -0
  30. monoco/features/agent/resources/en/skills/flow_engineer/SKILL.md +94 -0
  31. monoco/features/agent/resources/en/skills/flow_manager/SKILL.md +93 -0
  32. monoco/features/agent/resources/en/skills/flow_planner/SKILL.md +85 -0
  33. monoco/features/agent/resources/en/skills/flow_reviewer/SKILL.md +114 -0
  34. monoco/features/agent/resources/roles/role-engineer.yaml +49 -0
  35. monoco/features/agent/resources/roles/role-manager.yaml +46 -0
  36. monoco/features/agent/resources/roles/role-planner.yaml +46 -0
  37. monoco/features/agent/resources/roles/role-reviewer.yaml +47 -0
  38. monoco/features/agent/resources/workflows/workflow-dev.yaml +83 -0
  39. monoco/features/agent/resources/workflows/workflow-issue-create.yaml +72 -0
  40. monoco/features/agent/resources/workflows/workflow-review.yaml +94 -0
  41. monoco/features/agent/resources/zh/skills/flow_planner/SKILL.md +259 -0
  42. monoco/features/agent/resources/zh/skills/flow_reviewer/SKILL.md +137 -0
  43. monoco/features/agent/worker.py +38 -2
  44. monoco/features/glossary/__init__.py +0 -0
  45. monoco/features/glossary/adapter.py +31 -0
  46. monoco/features/glossary/config.py +5 -0
  47. monoco/features/glossary/resources/en/AGENTS.md +29 -0
  48. monoco/features/glossary/resources/en/skills/monoco_glossary/SKILL.md +35 -0
  49. monoco/features/glossary/resources/zh/AGENTS.md +29 -0
  50. monoco/features/glossary/resources/zh/skills/monoco_glossary/SKILL.md +35 -0
  51. monoco/features/i18n/resources/en/skills/i18n_scan_workflow/SKILL.md +105 -0
  52. monoco/features/i18n/resources/en/{SKILL.md → skills/monoco_i18n/SKILL.md} +2 -0
  53. monoco/features/i18n/resources/zh/{SKILL.md → skills/monoco_i18n/SKILL.md} +2 -0
  54. monoco/features/issue/core.py +45 -6
  55. monoco/features/issue/engine/machine.py +5 -2
  56. monoco/features/issue/models.py +1 -0
  57. monoco/features/issue/resources/en/skills/issue_create_workflow/SKILL.md +167 -0
  58. monoco/features/issue/resources/en/skills/issue_develop_workflow/SKILL.md +224 -0
  59. monoco/features/issue/resources/en/skills/issue_lifecycle_workflow/SKILL.md +159 -0
  60. monoco/features/issue/resources/en/skills/issue_refine_workflow/SKILL.md +203 -0
  61. monoco/features/issue/resources/en/{SKILL.md → skills/monoco_issue/SKILL.md} +2 -0
  62. monoco/features/issue/resources/zh/skills/issue_create_workflow/SKILL.md +167 -0
  63. monoco/features/issue/resources/zh/skills/issue_develop_workflow/SKILL.md +224 -0
  64. monoco/features/issue/resources/zh/skills/issue_refine_workflow/SKILL.md +203 -0
  65. monoco/features/issue/resources/zh/{SKILL.md → skills/monoco_issue/SKILL.md} +2 -0
  66. monoco/features/memo/resources/en/skills/monoco_memo/SKILL.md +77 -0
  67. monoco/features/memo/resources/en/skills/note_processing_workflow/SKILL.md +140 -0
  68. monoco/features/memo/resources/zh/{SKILL.md → skills/monoco_memo/SKILL.md} +2 -0
  69. monoco/features/spike/resources/en/{SKILL.md → skills/monoco_spike/SKILL.md} +2 -0
  70. monoco/features/spike/resources/en/skills/research_workflow/SKILL.md +121 -0
  71. monoco/features/spike/resources/zh/{SKILL.md → skills/monoco_spike/SKILL.md} +2 -0
  72. monoco_toolkit-0.3.10.dist-info/METADATA +124 -0
  73. monoco_toolkit-0.3.10.dist-info/RECORD +156 -0
  74. monoco/features/agent/reliability.py +0 -106
  75. monoco/features/agent/resources/skills/flow_reviewer/SKILL.md +0 -114
  76. monoco_toolkit-0.3.9.dist-info/METADATA +0 -127
  77. monoco_toolkit-0.3.9.dist-info/RECORD +0 -115
  78. /monoco/features/agent/resources/{skills → zh/skills}/flow_engineer/SKILL.md +0 -0
  79. /monoco/features/agent/resources/{skills → zh/skills}/flow_manager/SKILL.md +0 -0
  80. /monoco/features/i18n/resources/{skills → zh/skills}/i18n_scan_workflow/SKILL.md +0 -0
  81. /monoco/features/issue/resources/{skills → zh/skills}/issue_lifecycle_workflow/SKILL.md +0 -0
  82. /monoco/features/memo/resources/{skills → zh/skills}/note_processing_workflow/SKILL.md +0 -0
  83. /monoco/features/spike/resources/{skills → zh/skills}/research_workflow/SKILL.md +0 -0
  84. {monoco_toolkit-0.3.9.dist-info → monoco_toolkit-0.3.10.dist-info}/WHEEL +0 -0
  85. {monoco_toolkit-0.3.9.dist-info → monoco_toolkit-0.3.10.dist-info}/entry_points.txt +0 -0
  86. {monoco_toolkit-0.3.9.dist-info → monoco_toolkit-0.3.10.dist-info}/licenses/LICENSE +0 -0
monoco/core/sync.py CHANGED
@@ -46,6 +46,12 @@ def sync_command(
46
46
  help="Specific file to update (default: auto-detect from config or standard files)",
47
47
  ),
48
48
  check: bool = typer.Option(False, "--check", help="Dry run check mode"),
49
+ workflows: bool = typer.Option(
50
+ False,
51
+ "--workflows",
52
+ "-w",
53
+ help="Also distribute Flow Skills as Antigravity Workflows to .agent/workflows/",
54
+ ),
49
55
  ):
50
56
  """
51
57
  Synchronize Agent Environment (System Prompts & Skills).
@@ -84,7 +90,39 @@ def sync_command(
84
90
  f"[blue]Collected {len(collected_prompts)} prompts from {len(active_features)} features.[/blue]"
85
91
  )
86
92
 
87
- # 3. Distribute Skills
93
+
94
+
95
+ # 3. Distribute Roles
96
+ console.print("[bold blue]Distributing agent roles...[/bold blue]")
97
+
98
+ # Source: Builtin Resource Dir
99
+ # monoco/core/sync.py -> monoco/core -> monoco -> features/agent/resources/roles
100
+ resource_dir = Path(__file__).parent.parent / "features" / "agent" / "resources" / "roles"
101
+
102
+ # Target: .monoco/roles
103
+ target_roles_dir = root / ".monoco" / "roles"
104
+ # Only create if we have sources
105
+ if resource_dir.exists():
106
+ target_roles_dir.mkdir(parents=True, exist_ok=True)
107
+ import shutil
108
+
109
+ count = 0
110
+ for yaml_file in resource_dir.glob("*.yaml"):
111
+ target_file = target_roles_dir / yaml_file.name
112
+ try:
113
+ # Copy only if different or new? For now, nice and simple overwrite.
114
+ shutil.copy2(yaml_file, target_file)
115
+ console.print(f"[dim] ✓ Synced role {yaml_file.name}[/dim]")
116
+ count += 1
117
+ except Exception as e:
118
+ console.print(f"[red] Failed to sync role {yaml_file.name}: {e}[/red]")
119
+
120
+ if count > 0:
121
+ console.print(f"[green] ✓ Updated {count} roles in .monoco/roles/[/green]")
122
+ else:
123
+ console.print("[yellow] No builtin roles found to sync.[/yellow]")
124
+
125
+ # 4. Distribute Skills
88
126
  console.print("[bold blue]Distributing skills to agent frameworks...[/bold blue]")
89
127
 
90
128
  # Determine language from config
@@ -124,6 +162,26 @@ def sync_command(
124
162
  "[yellow]No agent frameworks detected. Skipping skill distribution.[/yellow]"
125
163
  )
126
164
 
165
+ # 5. Distribute Workflows (if --workflows flag is set)
166
+ if workflows:
167
+ console.print("[bold blue]Distributing Flow Skills as Workflows...[/bold blue]")
168
+
169
+ try:
170
+ workflow_results = skill_manager.distribute_workflows(force=False, lang=skill_lang)
171
+ success_count = sum(1 for v in workflow_results.values() if v)
172
+ if workflow_results:
173
+ console.print(
174
+ f"[green] ✓ Distributed {success_count}/{len(workflow_results)} workflows to .agent/workflows/[/green]"
175
+ )
176
+ else:
177
+ console.print(
178
+ "[yellow] No Flow Skills found to convert[/yellow]"
179
+ )
180
+ except Exception as e:
181
+ console.print(
182
+ f"[red] Failed to distribute workflows: {e}[/red]"
183
+ )
184
+
127
185
  # 4. Determine Targets
128
186
  targets = _get_targets(root, config, target)
129
187
 
@@ -238,3 +296,17 @@ def uninstall_command(
238
296
  console.print(
239
297
  "[yellow]No agent frameworks detected. Skipping skill cleanup.[/yellow]"
240
298
  )
299
+
300
+ # 3. Clean up Workflows
301
+ console.print("[bold blue]Cleaning up distributed workflows...[/bold blue]")
302
+
303
+ try:
304
+ removed_count = skill_manager.cleanup_workflows()
305
+ if removed_count > 0:
306
+ console.print(
307
+ f"[green] ✓ Removed {removed_count} workflows from .agent/workflows/[/green]"
308
+ )
309
+ except Exception as e:
310
+ console.print(
311
+ f"[red] Failed to clean workflows: {e}[/red]"
312
+ )
@@ -0,0 +1,420 @@
1
+ """
2
+ Flow Skill to Antigravity Workflow Converter.
3
+
4
+ This module converts Monoco Flow Skills to Antigravity Workflow format.
5
+
6
+ Conversion Rules:
7
+ 1. Frontmatter: Only keep 'description', discard other fields (name, type, role, version, author)
8
+ 2. Filename: monoco_flow_engineer/SKILL.md -> flow-engineer.md
9
+ 3. Content: Remove Mermaid state diagrams, convert to simple step lists
10
+ 4. Output: .agent/workflows/ directory
11
+ """
12
+
13
+ import re
14
+ from pathlib import Path
15
+ from typing import Optional, Tuple
16
+ import yaml
17
+ from rich.console import Console
18
+
19
+ console = Console()
20
+
21
+
22
+ class FlowSkillConverter:
23
+ """Converts Flow Skill files to Antigravity Workflow format."""
24
+
25
+ # Source directories to search for Flow Skills
26
+ SOURCE_PATTERNS = [
27
+ "monoco/features/agent/resources/{lang}/skills/flow_*/SKILL.md",
28
+ "monoco/features/*/resources/{lang}/skills/flow_*/SKILL.md",
29
+ ]
30
+
31
+ def __init__(self, root_dir: Path):
32
+ """
33
+ Initialize converter with project root directory.
34
+
35
+ Args:
36
+ root_dir: Project root directory
37
+ """
38
+ self.root_dir = root_dir
39
+
40
+ def _is_flow_skill(self, skill_file: Path) -> bool:
41
+ """
42
+ Check if a SKILL.md file is a Flow Skill by reading its frontmatter.
43
+
44
+ Args:
45
+ skill_file: Path to SKILL.md file
46
+
47
+ Returns:
48
+ True if the skill is a Flow Skill (type == "flow")
49
+ """
50
+ try:
51
+ content = skill_file.read_text(encoding="utf-8")
52
+ if not content.startswith("---"):
53
+ return False
54
+
55
+ parts = content.split("---", 2)
56
+ if len(parts) < 3:
57
+ return False
58
+
59
+ frontmatter = yaml.safe_load(parts[1].strip()) or {}
60
+ return frontmatter.get("type") == "flow"
61
+ except Exception:
62
+ return False
63
+
64
+ def discover_flow_skills(self, lang: str = "zh") -> list[Path]:
65
+ """
66
+ Discover all Flow Skill files in the source directories.
67
+
68
+ Flow Skills are discovered from monoco/features/*/resources/ directories,
69
+ not from the distributed .claude/skills/ directory.
70
+
71
+ Args:
72
+ lang: Language code (default: "zh")
73
+
74
+ Returns:
75
+ List of paths to Flow Skill SKILL.md files
76
+ """
77
+ flow_skills = []
78
+ seen_names = set()
79
+
80
+ # Search in source directories
81
+ features_dir = self.root_dir / "monoco" / "features"
82
+ if features_dir.exists():
83
+ for feature_dir in features_dir.iterdir():
84
+ if not feature_dir.is_dir():
85
+ continue
86
+
87
+ resources_dir = feature_dir / "resources" / lang / "skills"
88
+ if not resources_dir.exists():
89
+ continue
90
+
91
+ for skill_dir in resources_dir.iterdir():
92
+ if not skill_dir.is_dir():
93
+ continue
94
+
95
+ skill_file = skill_dir / "SKILL.md"
96
+ if skill_file.exists() and skill_dir.name not in seen_names:
97
+ # Check if it's a flow skill by reading frontmatter
98
+ if self._is_flow_skill(skill_file):
99
+ flow_skills.append(skill_file)
100
+ seen_names.add(skill_dir.name)
101
+
102
+ return sorted(flow_skills)
103
+
104
+ def convert_skill(self, skill_file: Path) -> Tuple[str, str]:
105
+ """
106
+ Convert a Flow Skill file to Antigravity Workflow format.
107
+
108
+ Args:
109
+ skill_file: Path to the Flow Skill SKILL.md file
110
+
111
+ Returns:
112
+ Tuple of (workflow_filename, workflow_content)
113
+ """
114
+ content = skill_file.read_text(encoding="utf-8")
115
+
116
+ # Parse frontmatter
117
+ frontmatter, body = self._extract_frontmatter(content)
118
+
119
+ # Convert frontmatter (keep only description)
120
+ new_frontmatter = self._convert_frontmatter(frontmatter)
121
+
122
+ # Convert body content
123
+ new_body = self._convert_body(body)
124
+
125
+ # Generate workflow filename
126
+ workflow_filename = self._generate_filename(skill_file)
127
+
128
+ # Combine
129
+ if new_frontmatter:
130
+ workflow_content = f"---\n{new_frontmatter}---\n\n{new_body}"
131
+ else:
132
+ workflow_content = new_body
133
+
134
+ return workflow_filename, workflow_content
135
+
136
+ def _extract_frontmatter(self, content: str) -> Tuple[dict, str]:
137
+ """
138
+ Extract YAML frontmatter from markdown content.
139
+
140
+ Args:
141
+ content: Full markdown content
142
+
143
+ Returns:
144
+ Tuple of (frontmatter_dict, body_content)
145
+ """
146
+ if not content.startswith("---"):
147
+ return {}, content
148
+
149
+ parts = content.split("---", 2)
150
+ if len(parts) < 3:
151
+ return {}, content
152
+
153
+ try:
154
+ frontmatter = yaml.safe_load(parts[1].strip()) or {}
155
+ except yaml.YAMLError:
156
+ frontmatter = {}
157
+
158
+ body = parts[2].strip()
159
+ return frontmatter, body
160
+
161
+ def _convert_frontmatter(self, frontmatter: dict) -> str:
162
+ """
163
+ Convert frontmatter to Antigravity Workflow format.
164
+
165
+ Only keeps 'description' field.
166
+
167
+ Args:
168
+ frontmatter: Original frontmatter dictionary
169
+
170
+ Returns:
171
+ New frontmatter as YAML string
172
+ """
173
+ description = frontmatter.get("description", "")
174
+
175
+ if not description:
176
+ return ""
177
+
178
+ # Create minimal frontmatter with only description
179
+ new_frontmatter = {"description": description}
180
+
181
+ return yaml.dump(new_frontmatter, allow_unicode=True, sort_keys=False)
182
+
183
+ def _convert_body(self, body: str) -> str:
184
+ """
185
+ Convert body content to Antigravity Workflow format.
186
+
187
+ - Remove Mermaid state diagrams
188
+ - Keep step sections but simplify
189
+ - Remove complex formatting
190
+
191
+ Args:
192
+ body: Original body content
193
+
194
+ Returns:
195
+ Converted body content
196
+ """
197
+ lines = body.split("\n")
198
+ result_lines = []
199
+ in_mermaid = False
200
+ skip_section = False
201
+
202
+ for line in lines:
203
+ # Detect Mermaid code block start
204
+ if line.strip().startswith("```mermaid"):
205
+ in_mermaid = True
206
+ continue
207
+
208
+ # Detect Mermaid code block end
209
+ if in_mermaid and line.strip() == "```":
210
+ in_mermaid = False
211
+ continue
212
+
213
+ # Skip lines inside Mermaid block
214
+ if in_mermaid:
215
+ continue
216
+
217
+ # Skip "工作流状态机" section header
218
+ if "工作流状态机" in line or "Workflow State Machine" in line:
219
+ skip_section = True
220
+ continue
221
+
222
+ # Detect new section (level 2 header)
223
+ if line.strip().startswith("## ") and skip_section:
224
+ skip_section = False
225
+
226
+ if skip_section:
227
+ continue
228
+
229
+ # Keep the line
230
+ result_lines.append(line)
231
+
232
+ # Clean up excessive blank lines
233
+ cleaned_lines = self._cleanup_blank_lines(result_lines)
234
+
235
+ return "\n".join(cleaned_lines)
236
+
237
+ def _cleanup_blank_lines(self, lines: list[str]) -> list[str]:
238
+ """
239
+ Clean up excessive blank lines while preserving structure.
240
+
241
+ Args:
242
+ lines: List of content lines
243
+
244
+ Returns:
245
+ Cleaned list of lines
246
+ """
247
+ result = []
248
+ prev_blank = False
249
+
250
+ for line in lines:
251
+ is_blank = not line.strip()
252
+
253
+ # Skip consecutive blank lines
254
+ if is_blank and prev_blank:
255
+ continue
256
+
257
+ result.append(line)
258
+ prev_blank = is_blank
259
+
260
+ # Remove trailing blank lines
261
+ while result and not result[-1].strip():
262
+ result.pop()
263
+
264
+ return result
265
+
266
+ def _generate_filename(self, skill_file: Path) -> str:
267
+ """
268
+ Generate workflow filename from skill file path.
269
+
270
+ Conversion: flow_engineer/SKILL.md -> flow-engineer.md
271
+
272
+ Args:
273
+ skill_file: Path to the Flow Skill SKILL.md file
274
+
275
+ Returns:
276
+ Workflow filename
277
+ """
278
+ # Get parent directory name (e.g., "flow_engineer")
279
+ skill_dir_name = skill_file.parent.name
280
+
281
+ # Remove "flow_" prefix
282
+ if skill_dir_name.startswith("flow_"):
283
+ role_name = skill_dir_name[len("flow_"):]
284
+ else:
285
+ role_name = skill_dir_name
286
+
287
+ # Convert to workflow filename
288
+ workflow_filename = f"flow-{role_name}.md"
289
+
290
+ return workflow_filename
291
+
292
+
293
+ class WorkflowDistributor:
294
+ """Distributes converted workflows to target directory."""
295
+
296
+ def __init__(self, root_dir: Path):
297
+ """
298
+ Initialize distributor.
299
+
300
+ Args:
301
+ root_dir: Project root directory
302
+ """
303
+ self.root_dir = root_dir
304
+ self.converter = FlowSkillConverter(root_dir)
305
+
306
+ def distribute(self, force: bool = False, lang: str = "zh") -> dict[str, bool]:
307
+ """
308
+ Convert and distribute all Flow Skills to .agent/workflows/.
309
+
310
+ Args:
311
+ force: Overwrite existing files even if unchanged
312
+ lang: Language code for Flow Skills (default: "zh")
313
+
314
+ Returns:
315
+ Dictionary mapping workflow filenames to success status
316
+ """
317
+ results = {}
318
+
319
+ # Discover flow skills
320
+ flow_skills = self.converter.discover_flow_skills(lang=lang)
321
+
322
+ if not flow_skills:
323
+ console.print("[yellow]No Flow Skills found to convert[/yellow]")
324
+ return results
325
+
326
+ # Target directory
327
+ workflows_dir = self.root_dir / ".agent" / "workflows"
328
+ workflows_dir.mkdir(parents=True, exist_ok=True)
329
+
330
+ console.print(f"[dim]Found {len(flow_skills)} Flow Skills to convert[/dim]")
331
+
332
+ for skill_file in flow_skills:
333
+ try:
334
+ workflow_filename, workflow_content = self.converter.convert_skill(skill_file)
335
+ target_file = workflows_dir / workflow_filename
336
+
337
+ # Check if update is needed
338
+ if target_file.exists() and not force:
339
+ existing_content = target_file.read_text(encoding="utf-8")
340
+ if existing_content == workflow_content:
341
+ console.print(f"[dim] = {workflow_filename} is up to date[/dim]")
342
+ results[workflow_filename] = True
343
+ continue
344
+
345
+ # Write workflow file
346
+ target_file.write_text(workflow_content, encoding="utf-8")
347
+ console.print(f"[green] ✓ Created {workflow_filename}[/green]")
348
+ results[workflow_filename] = True
349
+
350
+ except Exception as e:
351
+ console.print(f"[red] ✗ Failed to convert {skill_file.name}: {e}[/red]")
352
+ results[skill_file.name] = False
353
+
354
+ return results
355
+
356
+ def cleanup(self, lang: str = "zh") -> int:
357
+ """
358
+ Remove all distributed workflows from .agent/workflows/.
359
+
360
+ Args:
361
+ lang: Language code for Flow Skills (default: "zh")
362
+
363
+ Returns:
364
+ Number of files removed
365
+ """
366
+ workflows_dir = self.root_dir / ".agent" / "workflows"
367
+
368
+ if not workflows_dir.exists():
369
+ return 0
370
+
371
+ removed_count = 0
372
+
373
+ # Discover flow skills to know which files to remove
374
+ flow_skills = self.converter.discover_flow_skills(lang=lang)
375
+ workflow_filenames = set()
376
+
377
+ for skill_file in flow_skills:
378
+ workflow_filename = self.converter._generate_filename(skill_file)
379
+ workflow_filenames.add(workflow_filename)
380
+
381
+ # Remove workflow files
382
+ for workflow_file in workflows_dir.glob("flow-*.md"):
383
+ if workflow_file.name in workflow_filenames:
384
+ workflow_file.unlink()
385
+ console.print(f"[green] ✓ Removed {workflow_file.name}[/green]")
386
+ removed_count += 1
387
+
388
+ # Remove empty directory
389
+ if workflows_dir.exists() and not any(workflows_dir.iterdir()):
390
+ workflows_dir.rmdir()
391
+ console.print(f"[dim] Removed empty directory: {workflows_dir}[/dim]")
392
+
393
+ if removed_count == 0:
394
+ console.print(f"[dim]No workflows to remove from {workflows_dir}[/dim]")
395
+
396
+ return removed_count
397
+
398
+
399
+ def convert_flow_skill_to_workflow(skill_content: str) -> str:
400
+ """
401
+ Convert Flow Skill content to Antigravity Workflow format.
402
+
403
+ This is a standalone utility function for direct content conversion.
404
+
405
+ Args:
406
+ skill_content: Original Flow Skill markdown content
407
+
408
+ Returns:
409
+ Converted Workflow markdown content
410
+ """
411
+ converter = FlowSkillConverter(Path("."))
412
+
413
+ frontmatter, body = converter._extract_frontmatter(skill_content)
414
+ new_frontmatter = converter._convert_frontmatter(frontmatter)
415
+ new_body = converter._convert_body(body)
416
+
417
+ if new_frontmatter:
418
+ return f"---\n{new_frontmatter}---\n\n{new_body}"
419
+ else:
420
+ return new_body
@@ -1,10 +1,10 @@
1
- from .models import RoleTemplate, AgentConfig, SchedulerConfig
1
+ from .models import RoleTemplate, AgentRoleConfig as AgentConfig, SchedulerConfig
2
2
  from .worker import Worker
3
3
  from .config import load_scheduler_config, load_agent_config
4
4
  from .defaults import DEFAULT_ROLES
5
5
  from .session import Session, RuntimeSession
6
6
  from .manager import SessionManager
7
- from .reliability import ApoptosisManager
7
+ from .apoptosis import ApoptosisManager
8
8
 
9
9
  __all__ = [
10
10
  "RoleTemplate",
@@ -0,0 +1,31 @@
1
+ from pathlib import Path
2
+ from typing import Dict
3
+ from monoco.core.feature import MonocoFeature, IntegrationData
4
+
5
+
6
+ class AgentFeature(MonocoFeature):
7
+ @property
8
+ def name(self) -> str:
9
+ return "agent"
10
+
11
+ def initialize(self, root: Path, config: Dict) -> None:
12
+ # Agent feature doesn't require special initialization
13
+ pass
14
+
15
+ def integrate(self, root: Path, config: Dict) -> IntegrationData:
16
+ # Determine language from config, default to 'en'
17
+ lang = config.get("i18n", {}).get("source_lang", "en")
18
+
19
+ # Resource path: monoco/features/agent/resources/{lang}/AGENTS.md
20
+ base_dir = Path(__file__).parent / "resources"
21
+
22
+ # Try specific language, fallback to 'en'
23
+ prompt_file = base_dir / lang / "AGENTS.md"
24
+ if not prompt_file.exists():
25
+ prompt_file = base_dir / "en" / "AGENTS.md"
26
+
27
+ content = ""
28
+ if prompt_file.exists():
29
+ content = prompt_file.read_text(encoding="utf-8").strip()
30
+
31
+ return IntegrationData(system_prompts={"Agent": content})
@@ -0,0 +1,44 @@
1
+ from .manager import SessionManager
2
+ from .models import RoleTemplate
3
+
4
+ class ApoptosisManager:
5
+ """
6
+ Manages the controlled shutdown (Apoptosis) and investigation (Autopsy)
7
+ of failed agent sessions.
8
+ """
9
+
10
+ def __init__(self, session_manager: SessionManager):
11
+ self.session_manager = session_manager
12
+
13
+ def trigger_apoptosis(self, session_id: str) -> None:
14
+ """
15
+ Trigger the apoptosis process for a given session.
16
+ 1. Mark session as crashed.
17
+ 2. Spin up a Coroner agent to diagnose.
18
+ """
19
+ session = self.session_manager.get_session(session_id)
20
+ if not session:
21
+ return
22
+
23
+ # 1. Mark as crashed
24
+ session.model.status = "crashed"
25
+
26
+ # 2. Start Coroner
27
+ self._perform_autopsy(session)
28
+
29
+ def _perform_autopsy(self, victim_session):
30
+ coroner_role = RoleTemplate(
31
+ name="Coroner",
32
+ description="Investigates cause of death for failed agents.",
33
+ trigger="system.crash",
34
+ goal="Determine why the previous agent failed.",
35
+ system_prompt="You are the Coroner. Analyze the logs.",
36
+ engine="gemini"
37
+ )
38
+
39
+ coroner_session = self.session_manager.create_session(
40
+ issue_id=victim_session.model.issue_id,
41
+ role=coroner_role
42
+ )
43
+
44
+ coroner_session.start()