claude-mpm 2.0.0__py3-none-any.whl → 2.1.1__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.
@@ -0,0 +1,182 @@
1
+ """Agent Capabilities Content Generator.
2
+
3
+ This service generates markdown content for agent capabilities section
4
+ from discovered deployed agents.
5
+ """
6
+
7
+ from typing import List, Dict, Any
8
+ import logging
9
+
10
+ from jinja2 import Template
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class AgentCapabilitiesGenerator:
16
+ """Generates markdown content for agent capabilities section."""
17
+
18
+ def __init__(self):
19
+ """Initialize the generator with default template."""
20
+ self.template = self._load_template()
21
+ logger.debug("Initialized AgentCapabilitiesGenerator")
22
+
23
+ def generate_capabilities_section(self, deployed_agents: List[Dict[str, Any]]) -> str:
24
+ """Generate the complete agent capabilities markdown section.
25
+
26
+ Args:
27
+ deployed_agents: List of agent information dictionaries
28
+
29
+ Returns:
30
+ Generated markdown content for agent capabilities
31
+ """
32
+ try:
33
+ # Group agents by source tier for organized display
34
+ agents_by_tier = self._group_by_tier(deployed_agents)
35
+
36
+ # Generate core agent list
37
+ core_agent_list = self._generate_core_agent_list(deployed_agents)
38
+
39
+ # Generate detailed capabilities
40
+ detailed_capabilities = self._generate_detailed_capabilities(deployed_agents)
41
+
42
+ # Render template
43
+ content = self.template.render(
44
+ core_agents=core_agent_list,
45
+ detailed_capabilities=detailed_capabilities,
46
+ agents_by_tier=agents_by_tier,
47
+ total_agents=len(deployed_agents)
48
+ )
49
+
50
+ logger.info(f"Generated capabilities section for {len(deployed_agents)} agents")
51
+ return content
52
+
53
+ except Exception as e:
54
+ logger.error(f"Failed to generate capabilities section: {e}")
55
+ # Return fallback content on error
56
+ return self._generate_fallback_content()
57
+
58
+ def _group_by_tier(self, agents: List[Dict[str, Any]]) -> Dict[str, List[Dict[str, Any]]]:
59
+ """Group agents by their source tier.
60
+
61
+ Args:
62
+ agents: List of agent information dictionaries
63
+
64
+ Returns:
65
+ Dictionary mapping tiers to lists of agents
66
+ """
67
+ tiers = {'system': [], 'user': [], 'project': []}
68
+
69
+ for agent in agents:
70
+ tier = agent.get('source_tier', 'system')
71
+ if tier in tiers:
72
+ tiers[tier].append(agent)
73
+ else:
74
+ # Handle unknown tiers gracefully
75
+ tiers['system'].append(agent)
76
+ logger.warning(f"Unknown source tier '{tier}' for agent {agent.get('id')}, defaulting to system")
77
+
78
+ return tiers
79
+
80
+ def _generate_core_agent_list(self, agents: List[Dict[str, Any]]) -> str:
81
+ """Generate comma-separated list of core agent IDs.
82
+
83
+ Args:
84
+ agents: List of agent information dictionaries
85
+
86
+ Returns:
87
+ Comma-separated string of agent IDs
88
+ """
89
+ agent_ids = [agent['id'] for agent in agents]
90
+ return ', '.join(sorted(agent_ids))
91
+
92
+ def _generate_detailed_capabilities(self, agents: List[Dict[str, Any]]) -> List[Dict[str, str]]:
93
+ """Generate detailed capability descriptions for each agent.
94
+
95
+ Args:
96
+ agents: List of agent information dictionaries
97
+
98
+ Returns:
99
+ List of capability dictionaries for template rendering
100
+ """
101
+ capabilities = []
102
+
103
+ for agent in sorted(agents, key=lambda a: a['id']):
104
+ # Extract key capabilities
105
+ specializations = agent.get('specializations', [])
106
+ when_to_use = agent.get('capabilities', {}).get('when_to_use', [])
107
+
108
+ # Create capability summary
109
+ if when_to_use:
110
+ capability_text = '; '.join(when_to_use[:2]) # First 2 items
111
+ elif specializations:
112
+ capability_text = ', '.join(specializations[:3]) # First 3 specializations
113
+ else:
114
+ capability_text = agent.get('description', 'General purpose agent')
115
+
116
+ # Truncate long capability text
117
+ if len(capability_text) > 100:
118
+ capability_text = capability_text[:97] + '...'
119
+
120
+ capabilities.append({
121
+ 'name': agent['name'],
122
+ 'id': agent['id'],
123
+ 'capability_text': capability_text,
124
+ 'tools': ', '.join(agent.get('tools', [])[:5]) # First 5 tools
125
+ })
126
+
127
+ return capabilities
128
+
129
+ def _load_template(self) -> Template:
130
+ """Load the Jinja2 template for agent capabilities.
131
+
132
+ Returns:
133
+ Configured Jinja2 template
134
+ """
135
+ template_content = """
136
+ ## Agent Names & Capabilities
137
+ **Core Agents**: {{ core_agents }}
138
+
139
+ {% if agents_by_tier.project %}
140
+ ### Project-Specific Agents
141
+ {% for agent in agents_by_tier.project %}
142
+ - **{{ agent.name }}** ({{ agent.id }}): {{ agent.description }}
143
+ {% endfor %}
144
+
145
+ {% endif %}
146
+ **Agent Capabilities**:
147
+ {% for cap in detailed_capabilities %}
148
+ - **{{ cap.name }}**: {{ cap.capability_text }}
149
+ {% endfor %}
150
+
151
+ **Agent Name Formats** (both valid):
152
+ - Capitalized: {{ detailed_capabilities | map(attribute='name') | join('", "') }}
153
+ - Lowercase-hyphenated: {{ detailed_capabilities | map(attribute='id') | join('", "') }}
154
+
155
+ *Generated from {{ total_agents }} deployed agents*
156
+ """.strip()
157
+
158
+ return Template(template_content)
159
+
160
+ def _generate_fallback_content(self) -> str:
161
+ """Generate fallback content when agent discovery fails.
162
+
163
+ Returns:
164
+ Static fallback markdown content
165
+ """
166
+ logger.warning("Using fallback content due to generation failure")
167
+ return """
168
+ ## Agent Names & Capabilities
169
+ **Core Agents**: research, engineer, qa, documentation, security, ops, version_control, data_engineer
170
+
171
+ **Agent Capabilities**:
172
+ - **Research**: Codebase analysis, best practices, technical investigation
173
+ - **Engineer**: Implementation, refactoring, debugging
174
+ - **QA**: Quality assurance, testing, code review
175
+ - **Documentation**: Technical writing, API docs, user guides
176
+ - **Security**: Security analysis, vulnerability assessment
177
+ - **Ops**: Operations, deployment, infrastructure
178
+ - **Version Control**: Git operations, branch management
179
+ - **Data Engineer**: Data pipelines, ETL, database operations
180
+
181
+ *Note: Unable to dynamically generate agent list. Using default agents.*
182
+ """.strip()
@@ -63,6 +63,7 @@ class AgentDeploymentService:
63
63
  "errors": [],
64
64
  "skipped": [],
65
65
  "updated": [],
66
+ "migrated": [], # Track agents migrated from old format
66
67
  "total": 0
67
68
  }
68
69
 
@@ -87,28 +88,37 @@ class AgentDeploymentService:
87
88
  try:
88
89
  import json
89
90
  base_agent_data = json.loads(self.base_agent_path.read_text())
90
- base_agent_version = base_agent_data.get('version', 0)
91
- self.logger.info(f"Loaded base agent template (version {base_agent_version})")
91
+ # Handle both 'base_version' (new format) and 'version' (old format)
92
+ base_agent_version = self._parse_version(base_agent_data.get('base_version') or base_agent_data.get('version', 0))
93
+ self.logger.info(f"Loaded base agent template (version {self._format_version_display(base_agent_version)})")
92
94
  except Exception as e:
93
95
  self.logger.warning(f"Could not load base agent: {e}")
94
96
 
95
97
  # Get all template files
96
- template_files = list(self.templates_dir.glob("*_agent.json"))
98
+ template_files = list(self.templates_dir.glob("*.json"))
99
+ # Filter out non-agent files
100
+ template_files = [f for f in template_files if f.stem != "__init__" and not f.stem.startswith(".")]
97
101
  results["total"] = len(template_files)
98
102
 
99
103
  for template_file in template_files:
100
104
  try:
101
- agent_name = template_file.stem.replace("_agent", "")
105
+ agent_name = template_file.stem
102
106
  target_file = target_dir / f"{agent_name}.md"
103
107
 
104
108
  # Check if agent needs update
105
109
  needs_update = force_rebuild
110
+ is_migration = False
106
111
  if not needs_update and target_file.exists():
107
112
  needs_update, reason = self._check_agent_needs_update(
108
113
  target_file, template_file, base_agent_version
109
114
  )
110
115
  if needs_update:
111
- self.logger.info(f"Agent {agent_name} needs update: {reason}")
116
+ # Check if this is a migration from old format
117
+ if "migration needed" in reason:
118
+ is_migration = True
119
+ self.logger.info(f"Migrating agent {agent_name}: {reason}")
120
+ else:
121
+ self.logger.info(f"Agent {agent_name} needs update: {reason}")
112
122
 
113
123
  # Skip if exists and doesn't need update
114
124
  if target_file.exists() and not needs_update:
@@ -123,7 +133,15 @@ class AgentDeploymentService:
123
133
  is_update = target_file.exists()
124
134
  target_file.write_text(agent_md)
125
135
 
126
- if is_update:
136
+ if is_migration:
137
+ results["migrated"].append({
138
+ "name": agent_name,
139
+ "template": str(template_file),
140
+ "target": str(target_file),
141
+ "reason": reason
142
+ })
143
+ self.logger.info(f"Successfully migrated agent: {agent_name} to semantic versioning")
144
+ elif is_update:
127
145
  results["updated"].append({
128
146
  "name": agent_name,
129
147
  "template": str(template_file),
@@ -146,6 +164,7 @@ class AgentDeploymentService:
146
164
  self.logger.info(
147
165
  f"Deployed {len(results['deployed'])} agents, "
148
166
  f"updated {len(results['updated'])}, "
167
+ f"migrated {len(results['migrated'])}, "
149
168
  f"skipped {len(results['skipped'])}, "
150
169
  f"errors: {len(results['errors'])}"
151
170
  )
@@ -194,9 +213,14 @@ class AgentDeploymentService:
194
213
  template_data = json.loads(template_path.read_text())
195
214
 
196
215
  # Extract basic info
197
- agent_version = template_data.get('version', 0)
198
- base_version = base_agent_data.get('version', 0)
199
- version_string = f"{base_version:04d}-{agent_version:04d}"
216
+ # Handle both 'agent_version' (new format) and 'version' (old format)
217
+ agent_version = self._parse_version(template_data.get('agent_version') or template_data.get('version', 0))
218
+ base_version = self._parse_version(base_agent_data.get('base_version') or base_agent_data.get('version', 0))
219
+
220
+ # Format version string as semantic version
221
+ # Combine base and agent versions for a unified semantic version
222
+ # Use agent version as primary, with base version in metadata
223
+ version_string = self._format_version_display(agent_version)
200
224
 
201
225
  # Build YAML frontmatter
202
226
  description = (
@@ -219,6 +243,10 @@ author: "{template_data.get('author', 'claude-mpm@anthropic.com')}"
219
243
  created: "{datetime.now().isoformat()}Z"
220
244
  updated: "{datetime.now().isoformat()}Z"
221
245
  tags: {tags}
246
+ metadata:
247
+ base_version: "{self._format_version_display(base_version)}"
248
+ agent_version: "{self._format_version_display(agent_version)}"
249
+ deployment_type: "system"
222
250
  ---
223
251
 
224
252
  """
@@ -253,11 +281,12 @@ tags: {tags}
253
281
  template_data = json.loads(template_path.read_text())
254
282
 
255
283
  # Extract versions
256
- agent_version = template_data.get('version', 0)
257
- base_version = base_agent_data.get('version', 0)
284
+ # Handle both 'agent_version' (new format) and 'version' (old format)
285
+ agent_version = self._parse_version(template_data.get('agent_version') or template_data.get('version', 0))
286
+ base_version = self._parse_version(base_agent_data.get('base_version') or base_agent_data.get('version', 0))
258
287
 
259
- # Create version string in XXXX-YYYY format
260
- version_string = f"{base_version:04d}-{agent_version:04d}"
288
+ # Use semantic version format
289
+ version_string = self._format_version_display(agent_version)
261
290
 
262
291
  # Merge narrative fields (base + agent specific)
263
292
  narrative_fields = self._merge_narrative_fields(base_agent_data, template_data)
@@ -317,8 +346,8 @@ capabilities:
317
346
  # Agent Metadata
318
347
  metadata:
319
348
  source: "claude-mpm"
320
- template_version: {agent_version}
321
- base_version: {base_version}
349
+ template_version: "{self._format_version_display(agent_version)}"
350
+ base_version: "{self._format_version_display(base_version)}"
322
351
  deployment_type: "system"
323
352
 
324
353
  ...
@@ -441,6 +470,7 @@ metadata:
441
470
  results = {
442
471
  "config_dir": str(config_dir),
443
472
  "agents_found": [],
473
+ "agents_needing_migration": [],
444
474
  "environment": {},
445
475
  "warnings": []
446
476
  }
@@ -469,11 +499,19 @@ metadata:
469
499
  "path": str(agent_file)
470
500
  }
471
501
 
472
- # Extract name from YAML frontmatter
502
+ # Extract name and version from YAML frontmatter
503
+ version_str = None
473
504
  for line in lines:
474
505
  if line.startswith("name:"):
475
506
  agent_info["name"] = line.split(":", 1)[1].strip().strip('"\'')
476
- break
507
+ elif line.startswith("version:"):
508
+ version_str = line.split(":", 1)[1].strip().strip('"\'')
509
+ agent_info["version"] = version_str
510
+
511
+ # Check if agent needs migration
512
+ if version_str and self._is_old_version_format(version_str):
513
+ agent_info["needs_migration"] = True
514
+ results["agents_needing_migration"].append(agent_info["name"])
477
515
 
478
516
  results["agents_found"].append(agent_info)
479
517
 
@@ -504,11 +542,13 @@ metadata:
504
542
  self.logger.warning(f"Templates directory not found: {self.templates_dir}")
505
543
  return agents
506
544
 
507
- template_files = sorted(self.templates_dir.glob("*_agent.json"))
545
+ template_files = sorted(self.templates_dir.glob("*.json"))
546
+ # Filter out non-agent files
547
+ template_files = [f for f in template_files if f.stem != "__init__" and not f.stem.startswith(".")]
508
548
 
509
549
  for template_file in template_files:
510
550
  try:
511
- agent_name = template_file.stem.replace("_agent", "")
551
+ agent_name = template_file.stem
512
552
  agent_info = {
513
553
  "name": agent_name,
514
554
  "file": template_file.name,
@@ -521,11 +561,22 @@ metadata:
521
561
  try:
522
562
  import json
523
563
  template_data = json.loads(template_file.read_text())
524
- config_fields = template_data.get('configuration_fields', {})
525
564
 
526
- agent_info["role"] = config_fields.get('primary_role', '')
527
- agent_info["description"] = config_fields.get('description', agent_info["description"])
528
- agent_info["version"] = template_data.get('version', 0)
565
+ # Handle different schema formats
566
+ if 'metadata' in template_data:
567
+ # New schema format
568
+ metadata = template_data.get('metadata', {})
569
+ agent_info["description"] = metadata.get('description', agent_info["description"])
570
+ agent_info["role"] = metadata.get('specializations', [''])[0] if metadata.get('specializations') else ''
571
+ elif 'configuration_fields' in template_data:
572
+ # Old schema format
573
+ config_fields = template_data.get('configuration_fields', {})
574
+ agent_info["role"] = config_fields.get('primary_role', '')
575
+ agent_info["description"] = config_fields.get('description', agent_info["description"])
576
+
577
+ # Handle both 'agent_version' (new format) and 'version' (old format)
578
+ version_tuple = self._parse_version(template_data.get('agent_version') or template_data.get('version', 0))
579
+ agent_info["version"] = self._format_version_display(version_tuple)
529
580
 
530
581
  except Exception:
531
582
  pass # Use defaults if can't parse
@@ -537,7 +588,7 @@ metadata:
537
588
 
538
589
  return agents
539
590
 
540
- def _check_agent_needs_update(self, deployed_file: Path, template_file: Path, current_base_version: int) -> tuple:
591
+ def _check_agent_needs_update(self, deployed_file: Path, template_file: Path, current_base_version: tuple) -> tuple:
541
592
  """
542
593
  Check if a deployed agent needs to be updated.
543
594
 
@@ -554,32 +605,82 @@ metadata:
554
605
  deployed_content = deployed_file.read_text()
555
606
 
556
607
  # Check if it's a system agent (authored by claude-mpm)
557
- if "author: claude-mpm" not in deployed_content and "author: 'claude-mpm'" not in deployed_content:
608
+ if "claude-mpm" not in deployed_content:
558
609
  return (False, "not a system agent")
559
610
 
560
611
  # Extract version info from YAML frontmatter
561
612
  import re
562
613
 
563
- # Extract agent version from YAML
564
- agent_version_match = re.search(r"^agent_version:\s*(\d+)", deployed_content, re.MULTILINE)
565
- deployed_agent_version = int(agent_version_match.group(1)) if agent_version_match else 0
614
+ # Check if using old serial format first
615
+ is_old_format = False
616
+ old_version_str = None
617
+
618
+ # Try legacy combined format (e.g., "0002-0005")
619
+ legacy_match = re.search(r'^version:\s*["\']?(\d+)-(\d+)["\']?', deployed_content, re.MULTILINE)
620
+ if legacy_match:
621
+ is_old_format = True
622
+ old_version_str = f"{legacy_match.group(1)}-{legacy_match.group(2)}"
623
+ # Convert legacy format to semantic version
624
+ # Treat the agent version (second number) as minor version
625
+ deployed_agent_version = (0, int(legacy_match.group(2)), 0)
626
+ self.logger.info(f"Detected old serial version format: {old_version_str}")
627
+ else:
628
+ # Try to extract semantic version format (e.g., "2.1.0")
629
+ version_match = re.search(r'^version:\s*["\']?v?(\d+)\.(\d+)\.(\d+)["\']?', deployed_content, re.MULTILINE)
630
+ if version_match:
631
+ deployed_agent_version = (int(version_match.group(1)), int(version_match.group(2)), int(version_match.group(3)))
632
+ else:
633
+ # Fallback: try separate fields (very old format)
634
+ agent_version_match = re.search(r"^agent_version:\s*(\d+)", deployed_content, re.MULTILINE)
635
+ if agent_version_match:
636
+ is_old_format = True
637
+ old_version_str = f"agent_version: {agent_version_match.group(1)}"
638
+ deployed_agent_version = (0, int(agent_version_match.group(1)), 0)
639
+ self.logger.info(f"Detected old separate version format: {old_version_str}")
640
+ else:
641
+ # Check for missing version field
642
+ if "version:" not in deployed_content:
643
+ is_old_format = True
644
+ old_version_str = "missing"
645
+ deployed_agent_version = (0, 0, 0)
646
+ self.logger.info("Detected missing version field")
647
+ else:
648
+ deployed_agent_version = (0, 0, 0)
566
649
 
567
- # Extract base agent version from YAML
568
- base_version_match = re.search(r"^base_agent_version:\s*(\d+)", deployed_content, re.MULTILINE)
569
- deployed_base_version = int(base_version_match.group(1)) if base_version_match else 0
650
+ # For base version, we don't need to extract from deployed file anymore
651
+ # as it's tracked in metadata
570
652
 
571
653
  # Read template to get current agent version
572
654
  import json
573
655
  template_data = json.loads(template_file.read_text())
574
- current_agent_version = template_data.get('version', 0)
656
+
657
+ # Extract agent version from template (handle both numeric and semantic versioning)
658
+ current_agent_version = self._parse_version(template_data.get('agent_version') or template_data.get('version', 0))
659
+
660
+ # Compare semantic versions properly
661
+ # Semantic version comparison: compare major, then minor, then patch
662
+ def compare_versions(v1: tuple, v2: tuple) -> int:
663
+ """Compare two version tuples. Returns -1 if v1 < v2, 0 if equal, 1 if v1 > v2."""
664
+ for a, b in zip(v1, v2):
665
+ if a < b:
666
+ return -1
667
+ elif a > b:
668
+ return 1
669
+ return 0
670
+
671
+ # If old format detected, always trigger update for migration
672
+ if is_old_format:
673
+ new_version_str = self._format_version_display(current_agent_version)
674
+ return (True, f"migration needed from old format ({old_version_str}) to semantic version ({new_version_str})")
575
675
 
576
676
  # Check if agent template version is newer
577
- if current_agent_version > deployed_agent_version:
578
- return (True, f"agent template updated (v{deployed_agent_version:04d} -> v{current_agent_version:04d})")
677
+ if compare_versions(current_agent_version, deployed_agent_version) > 0:
678
+ deployed_str = self._format_version_display(deployed_agent_version)
679
+ current_str = self._format_version_display(current_agent_version)
680
+ return (True, f"agent template updated ({deployed_str} -> {current_str})")
579
681
 
580
- # Check if base agent version is newer
581
- if current_base_version > deployed_base_version:
582
- return (True, f"base agent updated (v{deployed_base_version:04d} -> v{current_base_version:04d})")
682
+ # Note: We no longer check base agent version separately since we're using
683
+ # a unified semantic version for the agent
583
684
 
584
685
  return (False, "up to date")
585
686
 
@@ -730,6 +831,96 @@ metadata:
730
831
  # Return specific tools or default set
731
832
  return agent_tools.get(agent_name, base_tools + ["Bash", "WebSearch"])
732
833
 
834
+ def _format_version_display(self, version_tuple: tuple) -> str:
835
+ """
836
+ Format version tuple for display.
837
+
838
+ Args:
839
+ version_tuple: Tuple of (major, minor, patch)
840
+
841
+ Returns:
842
+ Formatted version string
843
+ """
844
+ if isinstance(version_tuple, tuple) and len(version_tuple) == 3:
845
+ major, minor, patch = version_tuple
846
+ return f"{major}.{minor}.{patch}"
847
+ else:
848
+ # Fallback for legacy format
849
+ return str(version_tuple)
850
+
851
+ def _is_old_version_format(self, version_str: str) -> bool:
852
+ """
853
+ Check if a version string is in the old serial format.
854
+
855
+ Old formats include:
856
+ - Serial format: "0002-0005" (contains hyphen, all digits)
857
+ - Missing version field
858
+ - Non-semantic version formats
859
+
860
+ Args:
861
+ version_str: Version string to check
862
+
863
+ Returns:
864
+ True if old format, False if semantic version
865
+ """
866
+ if not version_str:
867
+ return True
868
+
869
+ import re
870
+
871
+ # Check for serial format (e.g., "0002-0005")
872
+ if re.match(r'^\d+-\d+$', version_str):
873
+ return True
874
+
875
+ # Check for semantic version format (e.g., "2.1.0")
876
+ if re.match(r'^v?\d+\.\d+\.\d+$', version_str):
877
+ return False
878
+
879
+ # Any other format is considered old
880
+ return True
881
+
882
+ def _parse_version(self, version_value: Any) -> tuple:
883
+ """
884
+ Parse version from various formats to semantic version tuple.
885
+
886
+ Handles:
887
+ - Integer values: 5 -> (0, 5, 0)
888
+ - String integers: "5" -> (0, 5, 0)
889
+ - Semantic versions: "2.1.0" -> (2, 1, 0)
890
+ - Invalid formats: returns (0, 0, 0)
891
+
892
+ Args:
893
+ version_value: Version in various formats
894
+
895
+ Returns:
896
+ Tuple of (major, minor, patch) for comparison
897
+ """
898
+ if isinstance(version_value, int):
899
+ # Legacy integer version - treat as minor version
900
+ return (0, version_value, 0)
901
+
902
+ if isinstance(version_value, str):
903
+ # Try to parse as simple integer
904
+ if version_value.isdigit():
905
+ return (0, int(version_value), 0)
906
+
907
+ # Try to parse semantic version (e.g., "2.1.0" or "v2.1.0")
908
+ import re
909
+ sem_ver_match = re.match(r'^v?(\d+)\.(\d+)\.(\d+)', version_value)
910
+ if sem_ver_match:
911
+ major = int(sem_ver_match.group(1))
912
+ minor = int(sem_ver_match.group(2))
913
+ patch = int(sem_ver_match.group(3))
914
+ return (major, minor, patch)
915
+
916
+ # Try to extract first number from string as minor version
917
+ num_match = re.search(r'(\d+)', version_value)
918
+ if num_match:
919
+ return (0, int(num_match.group(1)), 0)
920
+
921
+ # Default to 0.0.0 for invalid formats
922
+ return (0, 0, 0)
923
+
733
924
  def _format_yaml_list(self, items: List[str], indent: int) -> str:
734
925
  """
735
926
  Format a list for YAML with proper indentation.