claude-mpm 3.4.27__py3-none-any.whl → 3.5.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 (123) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +182 -299
  3. claude_mpm/agents/agent_loader.py +283 -57
  4. claude_mpm/agents/agent_loader_integration.py +6 -9
  5. claude_mpm/agents/base_agent.json +2 -1
  6. claude_mpm/agents/base_agent_loader.py +1 -1
  7. claude_mpm/cli/__init__.py +5 -7
  8. claude_mpm/cli/commands/__init__.py +0 -2
  9. claude_mpm/cli/commands/agents.py +1 -1
  10. claude_mpm/cli/commands/memory.py +1 -1
  11. claude_mpm/cli/commands/run.py +12 -0
  12. claude_mpm/cli/parser.py +0 -13
  13. claude_mpm/cli/utils.py +1 -1
  14. claude_mpm/config/__init__.py +44 -2
  15. claude_mpm/config/agent_config.py +348 -0
  16. claude_mpm/config/paths.py +322 -0
  17. claude_mpm/constants.py +0 -1
  18. claude_mpm/core/__init__.py +2 -5
  19. claude_mpm/core/agent_registry.py +63 -17
  20. claude_mpm/core/claude_runner.py +354 -43
  21. claude_mpm/core/config.py +7 -1
  22. claude_mpm/core/config_aliases.py +4 -3
  23. claude_mpm/core/config_paths.py +151 -0
  24. claude_mpm/core/factories.py +4 -50
  25. claude_mpm/core/logger.py +11 -13
  26. claude_mpm/core/service_registry.py +2 -2
  27. claude_mpm/dashboard/static/js/components/agent-inference.js +101 -25
  28. claude_mpm/dashboard/static/js/components/event-processor.js +3 -2
  29. claude_mpm/hooks/claude_hooks/hook_handler.py +343 -83
  30. claude_mpm/hooks/memory_integration_hook.py +1 -1
  31. claude_mpm/init.py +37 -6
  32. claude_mpm/scripts/socketio_daemon.py +6 -2
  33. claude_mpm/services/__init__.py +71 -3
  34. claude_mpm/services/agents/__init__.py +85 -0
  35. claude_mpm/services/agents/deployment/__init__.py +21 -0
  36. claude_mpm/services/{agent_deployment.py → agents/deployment/agent_deployment.py} +192 -41
  37. claude_mpm/services/{agent_lifecycle_manager.py → agents/deployment/agent_lifecycle_manager.py} +11 -10
  38. claude_mpm/services/agents/loading/__init__.py +11 -0
  39. claude_mpm/services/{agent_profile_loader.py → agents/loading/agent_profile_loader.py} +9 -8
  40. claude_mpm/services/{base_agent_manager.py → agents/loading/base_agent_manager.py} +2 -2
  41. claude_mpm/services/{framework_agent_loader.py → agents/loading/framework_agent_loader.py} +116 -40
  42. claude_mpm/services/agents/management/__init__.py +9 -0
  43. claude_mpm/services/{agent_management_service.py → agents/management/agent_management_service.py} +6 -5
  44. claude_mpm/services/agents/memory/__init__.py +21 -0
  45. claude_mpm/services/{agent_memory_manager.py → agents/memory/agent_memory_manager.py} +3 -3
  46. claude_mpm/services/agents/registry/__init__.py +29 -0
  47. claude_mpm/services/{agent_registry.py → agents/registry/agent_registry.py} +101 -16
  48. claude_mpm/services/{deployed_agent_discovery.py → agents/registry/deployed_agent_discovery.py} +12 -2
  49. claude_mpm/services/{agent_modification_tracker.py → agents/registry/modification_tracker.py} +6 -5
  50. claude_mpm/services/async_session_logger.py +584 -0
  51. claude_mpm/services/claude_session_logger.py +299 -0
  52. claude_mpm/services/framework_claude_md_generator/content_assembler.py +2 -2
  53. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +17 -17
  54. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +3 -3
  55. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +1 -1
  56. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +1 -1
  57. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +19 -24
  58. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +1 -1
  59. claude_mpm/services/framework_claude_md_generator.py +4 -2
  60. claude_mpm/services/memory/__init__.py +17 -0
  61. claude_mpm/services/{memory_builder.py → memory/builder.py} +3 -3
  62. claude_mpm/services/memory/cache/__init__.py +14 -0
  63. claude_mpm/services/{shared_prompt_cache.py → memory/cache/shared_prompt_cache.py} +1 -1
  64. claude_mpm/services/memory/cache/simple_cache.py +317 -0
  65. claude_mpm/services/{memory_optimizer.py → memory/optimizer.py} +1 -1
  66. claude_mpm/services/{memory_router.py → memory/router.py} +1 -1
  67. claude_mpm/services/optimized_hook_service.py +542 -0
  68. claude_mpm/services/project_registry.py +14 -8
  69. claude_mpm/services/response_tracker.py +237 -0
  70. claude_mpm/services/ticketing_service_original.py +4 -2
  71. claude_mpm/services/version_control/branch_strategy.py +3 -1
  72. claude_mpm/utils/paths.py +12 -10
  73. claude_mpm/utils/session_logging.py +114 -0
  74. claude_mpm/validation/agent_validator.py +2 -1
  75. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/METADATA +26 -20
  76. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/RECORD +83 -106
  77. claude_mpm/cli/commands/ui.py +0 -57
  78. claude_mpm/core/simple_runner.py +0 -1046
  79. claude_mpm/hooks/builtin/__init__.py +0 -1
  80. claude_mpm/hooks/builtin/logging_hook_example.py +0 -165
  81. claude_mpm/hooks/builtin/memory_hooks_example.py +0 -67
  82. claude_mpm/hooks/builtin/mpm_command_hook.py +0 -125
  83. claude_mpm/hooks/builtin/post_delegation_hook_example.py +0 -124
  84. claude_mpm/hooks/builtin/pre_delegation_hook_example.py +0 -125
  85. claude_mpm/hooks/builtin/submit_hook_example.py +0 -100
  86. claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +0 -237
  87. claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +0 -240
  88. claude_mpm/hooks/builtin/workflow_start_hook.py +0 -181
  89. claude_mpm/orchestration/__init__.py +0 -6
  90. claude_mpm/orchestration/archive/direct_orchestrator.py +0 -195
  91. claude_mpm/orchestration/archive/factory.py +0 -215
  92. claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +0 -188
  93. claude_mpm/orchestration/archive/hook_integration_example.py +0 -178
  94. claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +0 -826
  95. claude_mpm/orchestration/archive/orchestrator.py +0 -501
  96. claude_mpm/orchestration/archive/pexpect_orchestrator.py +0 -252
  97. claude_mpm/orchestration/archive/pty_orchestrator.py +0 -270
  98. claude_mpm/orchestration/archive/simple_orchestrator.py +0 -82
  99. claude_mpm/orchestration/archive/subprocess_orchestrator.py +0 -801
  100. claude_mpm/orchestration/archive/system_prompt_orchestrator.py +0 -278
  101. claude_mpm/orchestration/archive/wrapper_orchestrator.py +0 -187
  102. claude_mpm/schemas/workflow_validator.py +0 -411
  103. claude_mpm/services/parent_directory_manager/__init__.py +0 -577
  104. claude_mpm/services/parent_directory_manager/backup_manager.py +0 -258
  105. claude_mpm/services/parent_directory_manager/config_manager.py +0 -210
  106. claude_mpm/services/parent_directory_manager/deduplication_manager.py +0 -279
  107. claude_mpm/services/parent_directory_manager/framework_protector.py +0 -143
  108. claude_mpm/services/parent_directory_manager/operations.py +0 -186
  109. claude_mpm/services/parent_directory_manager/state_manager.py +0 -624
  110. claude_mpm/services/parent_directory_manager/template_deployer.py +0 -579
  111. claude_mpm/services/parent_directory_manager/validation_manager.py +0 -378
  112. claude_mpm/services/parent_directory_manager/version_control_helper.py +0 -339
  113. claude_mpm/services/parent_directory_manager/version_manager.py +0 -222
  114. claude_mpm/ui/__init__.py +0 -1
  115. claude_mpm/ui/rich_terminal_ui.py +0 -295
  116. claude_mpm/ui/terminal_ui.py +0 -328
  117. /claude_mpm/services/{agent_versioning.py → agents/deployment/agent_versioning.py} +0 -0
  118. /claude_mpm/services/{agent_capabilities_generator.py → agents/management/agent_capabilities_generator.py} +0 -0
  119. /claude_mpm/services/{agent_persistence_service.py → agents/memory/agent_persistence_service.py} +0 -0
  120. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/WHEEL +0 -0
  121. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/entry_points.txt +0 -0
  122. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/licenses/LICENSE +0 -0
  123. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/top_level.txt +0 -0
@@ -1,411 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Ticket Workflow Schema Validator
4
- ================================
5
-
6
- Utility module for validating and working with ticket workflow schemas.
7
-
8
- WHY: This module exists to ensure workflow definitions are valid and consistent
9
- before they are used in the ticketing system. By validating workflows at load
10
- time, we prevent runtime errors and ensure all workflows follow the same structure.
11
-
12
- DESIGN DECISIONS:
13
- - Uses jsonschema for validation to leverage existing JSON Schema standards
14
- - Provides both validation and helper functions for common workflow operations
15
- - Returns detailed error messages to help users fix invalid workflows
16
- - Validates business logic beyond just schema structure
17
-
18
- Usage:
19
- from claude_mpm.schemas.workflow_validator import WorkflowValidator
20
-
21
- validator = WorkflowValidator()
22
-
23
- # Validate a workflow
24
- errors = validator.validate_workflow(workflow_dict)
25
- if errors:
26
- print(f"Validation failed: {errors}")
27
-
28
- # Load and validate from file
29
- workflow = validator.load_workflow_file("standard_workflow.json")
30
- """
31
-
32
- import json
33
- import logging
34
- from pathlib import Path
35
- from typing import Any, Dict, List, Optional, Set, Tuple
36
-
37
- try:
38
- import jsonschema
39
- from jsonschema import Draft7Validator, validators
40
- JSONSCHEMA_AVAILABLE = True
41
- except ImportError:
42
- JSONSCHEMA_AVAILABLE = False
43
- logging.warning("jsonschema not available, workflow validation disabled")
44
-
45
- logger = logging.getLogger(__name__)
46
-
47
-
48
- class WorkflowValidator:
49
- """
50
- Validates ticket workflow definitions against the schema.
51
-
52
- WHY: Ensures workflow definitions are valid before use, preventing
53
- runtime errors and ensuring consistency across all workflows.
54
- """
55
-
56
- def __init__(self, schema_path: Optional[Path] = None):
57
- """
58
- Initialize the workflow validator.
59
-
60
- Args:
61
- schema_path: Path to the workflow schema file. If not provided,
62
- uses the default schema location.
63
- """
64
- self.schema_path = schema_path or self._get_default_schema_path()
65
- self.schema = self._load_schema()
66
- self._validator = None
67
-
68
- if JSONSCHEMA_AVAILABLE and self.schema:
69
- self._validator = Draft7Validator(self.schema)
70
-
71
- def _get_default_schema_path(self) -> Path:
72
- """Get the default path to the workflow schema."""
73
- return Path(__file__).parent / "ticket_workflow_schema.json"
74
-
75
- def _load_schema(self) -> Optional[Dict[str, Any]]:
76
- """Load the workflow schema from file."""
77
- try:
78
- if self.schema_path.exists():
79
- with open(self.schema_path, 'r') as f:
80
- return json.load(f)
81
- else:
82
- logger.error(f"Schema file not found: {self.schema_path}")
83
- return None
84
- except Exception as e:
85
- logger.error(f"Failed to load schema: {e}")
86
- return None
87
-
88
- def validate_workflow(self, workflow: Dict[str, Any]) -> List[str]:
89
- """
90
- Validate a workflow definition.
91
-
92
- WHY: Validates both schema structure and business logic to ensure
93
- the workflow is not only syntactically correct but also logically
94
- consistent and usable.
95
-
96
- Args:
97
- workflow: Workflow definition dictionary
98
-
99
- Returns:
100
- List of validation error messages (empty if valid)
101
- """
102
- errors = []
103
-
104
- # Schema validation
105
- if JSONSCHEMA_AVAILABLE and self._validator:
106
- for error in self._validator.iter_errors(workflow):
107
- errors.append(f"Schema error at {'.'.join(str(p) for p in error.path)}: {error.message}")
108
-
109
- # Business logic validation
110
- if not errors: # Only validate logic if schema is valid
111
- logic_errors = self._validate_business_logic(workflow)
112
- errors.extend(logic_errors)
113
-
114
- return errors
115
-
116
- def _validate_business_logic(self, workflow: Dict[str, Any]) -> List[str]:
117
- """
118
- Validate workflow business logic beyond schema requirements.
119
-
120
- WHY: Schema validation ensures structure, but we also need to validate
121
- that the workflow makes logical sense (e.g., transitions reference
122
- existing statuses, mappings are consistent, etc.)
123
- """
124
- errors = []
125
-
126
- # Extract status and resolution IDs
127
- status_ids = {s['id'] for s in workflow.get('status_states', {}).get('states', [])}
128
- resolution_ids = {r['id'] for r in workflow.get('resolution_types', {}).get('types', [])}
129
-
130
- # Validate default status exists
131
- default_status = workflow.get('status_states', {}).get('default_status')
132
- if default_status and default_status not in status_ids:
133
- errors.append(f"Default status '{default_status}' not found in status definitions")
134
-
135
- # Validate transitions reference existing statuses
136
- for i, transition in enumerate(workflow.get('transitions', {}).get('rules', [])):
137
- from_status = transition.get('from_status')
138
- to_status = transition.get('to_status')
139
-
140
- if from_status != '*' and from_status not in status_ids:
141
- errors.append(f"Transition {i}: from_status '{from_status}' not found")
142
-
143
- if to_status not in status_ids:
144
- errors.append(f"Transition {i}: to_status '{to_status}' not found")
145
-
146
- # Validate status-resolution mappings
147
- for i, mapping in enumerate(workflow.get('status_resolution_mapping', {}).get('mappings', [])):
148
- status_id = mapping.get('status_id')
149
- if status_id not in status_ids:
150
- errors.append(f"Status-resolution mapping {i}: status '{status_id}' not found")
151
-
152
- # Check resolution IDs (unless wildcard)
153
- for resolution in mapping.get('allowed_resolutions', []):
154
- if resolution != '*' and resolution not in resolution_ids:
155
- errors.append(f"Status-resolution mapping {i}: resolution '{resolution}' not found")
156
-
157
- # Check default resolution exists in allowed resolutions
158
- default_res = mapping.get('default_resolution')
159
- allowed_res = mapping.get('allowed_resolutions', [])
160
- if default_res and '*' not in allowed_res and default_res not in allowed_res:
161
- errors.append(f"Status-resolution mapping {i}: default resolution '{default_res}' not in allowed resolutions")
162
-
163
- # Validate there's at least one initial status
164
- initial_statuses = [s for s in workflow.get('status_states', {}).get('states', [])
165
- if s.get('category') == 'initial']
166
- if not initial_statuses:
167
- errors.append("No initial status defined (category='initial')")
168
-
169
- # Validate there's at least one terminal status
170
- terminal_statuses = [s for s in workflow.get('status_states', {}).get('states', [])
171
- if s.get('category') == 'terminal']
172
- if not terminal_statuses:
173
- errors.append("No terminal status defined (category='terminal')")
174
-
175
- # Validate escalation rules reference existing statuses
176
- for i, rule in enumerate(workflow.get('business_rules', {}).get('escalation_rules', [])):
177
- condition_status = rule.get('condition', {}).get('status')
178
- if condition_status and condition_status not in status_ids:
179
- errors.append(f"Escalation rule {i}: condition status '{condition_status}' not found")
180
-
181
- action_status = rule.get('action', {}).get('change_status')
182
- if action_status and action_status not in status_ids:
183
- errors.append(f"Escalation rule {i}: action status '{action_status}' not found")
184
-
185
- return errors
186
-
187
- def load_workflow_file(self, filepath: Path) -> Optional[Dict[str, Any]]:
188
- """
189
- Load and validate a workflow from file.
190
-
191
- Args:
192
- filepath: Path to workflow JSON file
193
-
194
- Returns:
195
- Validated workflow dictionary or None if invalid
196
- """
197
- try:
198
- with open(filepath, 'r') as f:
199
- workflow = json.load(f)
200
-
201
- errors = self.validate_workflow(workflow)
202
- if errors:
203
- logger.error(f"Workflow validation failed for {filepath}:")
204
- for error in errors:
205
- logger.error(f" - {error}")
206
- return None
207
-
208
- return workflow
209
-
210
- except Exception as e:
211
- logger.error(f"Failed to load workflow from {filepath}: {e}")
212
- return None
213
-
214
- def get_valid_transitions(self, workflow: Dict[str, Any], from_status: str) -> List[Dict[str, Any]]:
215
- """
216
- Get valid transitions from a given status.
217
-
218
- WHY: Helper function to easily determine what transitions are available
219
- from a specific status, supporting both specific and wildcard rules.
220
-
221
- Args:
222
- workflow: Workflow definition
223
- from_status: Current status ID
224
-
225
- Returns:
226
- List of valid transition rules
227
- """
228
- transitions = []
229
-
230
- for rule in workflow.get('transitions', {}).get('rules', []):
231
- rule_from = rule.get('from_status')
232
- if rule_from == from_status or rule_from == '*':
233
- transitions.append(rule)
234
-
235
- return transitions
236
-
237
- def get_allowed_resolutions(self, workflow: Dict[str, Any], status: str) -> Tuple[List[str], bool]:
238
- """
239
- Get allowed resolutions for a given status.
240
-
241
- WHY: Helper function to determine which resolutions are valid for a
242
- specific status, and whether a resolution is required.
243
-
244
- Args:
245
- workflow: Workflow definition
246
- status: Status ID
247
-
248
- Returns:
249
- Tuple of (allowed_resolution_ids, is_required)
250
- """
251
- for mapping in workflow.get('status_resolution_mapping', {}).get('mappings', []):
252
- if mapping.get('status_id') == status:
253
- allowed = mapping.get('allowed_resolutions', [])
254
- required = mapping.get('requires_resolution', False)
255
-
256
- # Handle wildcard
257
- if '*' in allowed:
258
- all_resolutions = [r['id'] for r in workflow.get('resolution_types', {}).get('types', [])]
259
- return all_resolutions, required
260
-
261
- return allowed, required
262
-
263
- return [], False
264
-
265
- def validate_transition(self, workflow: Dict[str, Any], from_status: str,
266
- to_status: str, data: Dict[str, Any]) -> List[str]:
267
- """
268
- Validate a specific status transition.
269
-
270
- WHY: Ensures a proposed transition is valid according to the workflow
271
- rules and that all required fields are provided.
272
-
273
- Args:
274
- workflow: Workflow definition
275
- from_status: Current status
276
- to_status: Target status
277
- data: Transition data (should include required fields)
278
-
279
- Returns:
280
- List of validation errors (empty if valid)
281
- """
282
- errors = []
283
-
284
- # Find applicable transition rules
285
- valid_transitions = self.get_valid_transitions(workflow, from_status)
286
- matching_rule = None
287
-
288
- for rule in valid_transitions:
289
- if rule.get('to_status') == to_status:
290
- matching_rule = rule
291
- break
292
-
293
- if not matching_rule:
294
- errors.append(f"No valid transition from '{from_status}' to '{to_status}'")
295
- return errors
296
-
297
- # Check required fields
298
- required_fields = matching_rule.get('required_fields', [])
299
- for field in required_fields:
300
- if field not in data or data[field] is None:
301
- errors.append(f"Required field '{field}' missing for transition")
302
-
303
- # If transitioning to a terminal status, check resolution requirements
304
- target_status = next((s for s in workflow.get('status_states', {}).get('states', [])
305
- if s['id'] == to_status), None)
306
-
307
- if target_status and target_status.get('category') == 'terminal':
308
- allowed_resolutions, requires_resolution = self.get_allowed_resolutions(workflow, to_status)
309
-
310
- if requires_resolution and 'resolution' not in data:
311
- errors.append(f"Resolution required for status '{to_status}'")
312
- elif 'resolution' in data and data['resolution'] not in allowed_resolutions:
313
- errors.append(f"Resolution '{data['resolution']}' not allowed for status '{to_status}'")
314
-
315
- return errors
316
-
317
-
318
- def create_example_workflows():
319
- """
320
- Create example workflow files for testing and demonstration.
321
-
322
- WHY: Provides ready-to-use workflow examples that demonstrate different
323
- use cases and configuration options.
324
- """
325
- examples_dir = Path(__file__).parent / "examples"
326
- examples_dir.mkdir(exist_ok=True)
327
-
328
- # Bug tracking workflow
329
- bug_workflow = {
330
- "schema_version": "1.0.0",
331
- "workflow_id": "bug_tracking",
332
- "workflow_version": "1.0.0",
333
- "metadata": {
334
- "name": "Bug Tracking Workflow",
335
- "description": "Workflow optimized for software bug tracking",
336
- "workflow_type": "bug_tracking"
337
- },
338
- "status_states": {
339
- "states": [
340
- {"id": "reported", "name": "Reported", "category": "initial"},
341
- {"id": "confirmed", "name": "Confirmed", "category": "active"},
342
- {"id": "in_progress", "name": "In Progress", "category": "active"},
343
- {"id": "fixed", "name": "Fixed", "category": "active"},
344
- {"id": "verified", "name": "Verified", "category": "terminal"},
345
- {"id": "closed", "name": "Closed", "category": "terminal"},
346
- {"id": "rejected", "name": "Rejected", "category": "terminal"}
347
- ]
348
- },
349
- "resolution_types": {
350
- "types": [
351
- {"id": "fixed", "name": "Fixed", "category": "successful"},
352
- {"id": "cannot_reproduce", "name": "Cannot Reproduce", "category": "invalid"},
353
- {"id": "duplicate", "name": "Duplicate", "category": "invalid"},
354
- {"id": "by_design", "name": "By Design", "category": "invalid"},
355
- {"id": "wont_fix", "name": "Won't Fix", "category": "unsuccessful"}
356
- ]
357
- },
358
- "transitions": {
359
- "rules": [
360
- {"from_status": "reported", "to_status": "confirmed", "name": "Confirm Bug"},
361
- {"from_status": "reported", "to_status": "rejected", "name": "Reject"},
362
- {"from_status": "confirmed", "to_status": "in_progress", "name": "Start Fix"},
363
- {"from_status": "in_progress", "to_status": "fixed", "name": "Mark Fixed"},
364
- {"from_status": "fixed", "to_status": "verified", "name": "Verify Fix"},
365
- {"from_status": "verified", "to_status": "closed", "name": "Close"}
366
- ]
367
- },
368
- "status_resolution_mapping": {
369
- "mappings": [
370
- {
371
- "status_id": "verified",
372
- "allowed_resolutions": ["fixed"],
373
- "requires_resolution": true
374
- },
375
- {
376
- "status_id": "closed",
377
- "allowed_resolutions": ["*"],
378
- "requires_resolution": true
379
- },
380
- {
381
- "status_id": "rejected",
382
- "allowed_resolutions": ["cannot_reproduce", "duplicate", "by_design", "wont_fix"],
383
- "requires_resolution": true
384
- }
385
- ]
386
- }
387
- }
388
-
389
- with open(examples_dir / "bug_tracking_workflow.json", 'w') as f:
390
- json.dump(bug_workflow, f, indent=2)
391
-
392
- logger.info("Created example workflow files")
393
-
394
-
395
- if __name__ == "__main__":
396
- # Example usage
397
- validator = WorkflowValidator()
398
-
399
- # Load and validate the standard workflow
400
- example_path = Path(__file__).parent / "examples" / "standard_workflow.json"
401
- if example_path.exists():
402
- workflow = validator.load_workflow_file(example_path)
403
- if workflow:
404
- print(f"Successfully loaded workflow: {workflow['metadata']['name']}")
405
-
406
- # Test some helper functions
407
- transitions = validator.get_valid_transitions(workflow, "open")
408
- print(f"\nValid transitions from 'open': {[t['name'] for t in transitions]}")
409
-
410
- resolutions, required = validator.get_allowed_resolutions(workflow, "resolved")
411
- print(f"\nResolutions for 'resolved' status: {resolutions} (required: {required})")