crackerjack 0.29.0__py3-none-any.whl → 0.31.4__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.

Potentially problematic release.


This version of crackerjack might be problematic. Click here for more details.

Files changed (158) hide show
  1. crackerjack/CLAUDE.md +1005 -0
  2. crackerjack/RULES.md +380 -0
  3. crackerjack/__init__.py +42 -13
  4. crackerjack/__main__.py +225 -253
  5. crackerjack/agents/__init__.py +41 -0
  6. crackerjack/agents/architect_agent.py +281 -0
  7. crackerjack/agents/base.py +169 -0
  8. crackerjack/agents/coordinator.py +512 -0
  9. crackerjack/agents/documentation_agent.py +498 -0
  10. crackerjack/agents/dry_agent.py +388 -0
  11. crackerjack/agents/formatting_agent.py +245 -0
  12. crackerjack/agents/import_optimization_agent.py +281 -0
  13. crackerjack/agents/performance_agent.py +669 -0
  14. crackerjack/agents/proactive_agent.py +104 -0
  15. crackerjack/agents/refactoring_agent.py +788 -0
  16. crackerjack/agents/security_agent.py +529 -0
  17. crackerjack/agents/test_creation_agent.py +652 -0
  18. crackerjack/agents/test_specialist_agent.py +486 -0
  19. crackerjack/agents/tracker.py +212 -0
  20. crackerjack/api.py +560 -0
  21. crackerjack/cli/__init__.py +24 -0
  22. crackerjack/cli/facade.py +104 -0
  23. crackerjack/cli/handlers.py +267 -0
  24. crackerjack/cli/interactive.py +471 -0
  25. crackerjack/cli/options.py +401 -0
  26. crackerjack/cli/utils.py +18 -0
  27. crackerjack/code_cleaner.py +670 -0
  28. crackerjack/config/__init__.py +19 -0
  29. crackerjack/config/hooks.py +218 -0
  30. crackerjack/core/__init__.py +0 -0
  31. crackerjack/core/async_workflow_orchestrator.py +406 -0
  32. crackerjack/core/autofix_coordinator.py +200 -0
  33. crackerjack/core/container.py +104 -0
  34. crackerjack/core/enhanced_container.py +542 -0
  35. crackerjack/core/performance.py +243 -0
  36. crackerjack/core/phase_coordinator.py +561 -0
  37. crackerjack/core/proactive_workflow.py +316 -0
  38. crackerjack/core/session_coordinator.py +289 -0
  39. crackerjack/core/workflow_orchestrator.py +640 -0
  40. crackerjack/dynamic_config.py +577 -0
  41. crackerjack/errors.py +263 -41
  42. crackerjack/executors/__init__.py +11 -0
  43. crackerjack/executors/async_hook_executor.py +431 -0
  44. crackerjack/executors/cached_hook_executor.py +242 -0
  45. crackerjack/executors/hook_executor.py +345 -0
  46. crackerjack/executors/individual_hook_executor.py +669 -0
  47. crackerjack/intelligence/__init__.py +44 -0
  48. crackerjack/intelligence/adaptive_learning.py +751 -0
  49. crackerjack/intelligence/agent_orchestrator.py +551 -0
  50. crackerjack/intelligence/agent_registry.py +414 -0
  51. crackerjack/intelligence/agent_selector.py +502 -0
  52. crackerjack/intelligence/integration.py +290 -0
  53. crackerjack/interactive.py +576 -315
  54. crackerjack/managers/__init__.py +11 -0
  55. crackerjack/managers/async_hook_manager.py +135 -0
  56. crackerjack/managers/hook_manager.py +137 -0
  57. crackerjack/managers/publish_manager.py +411 -0
  58. crackerjack/managers/test_command_builder.py +151 -0
  59. crackerjack/managers/test_executor.py +435 -0
  60. crackerjack/managers/test_manager.py +258 -0
  61. crackerjack/managers/test_manager_backup.py +1124 -0
  62. crackerjack/managers/test_progress.py +144 -0
  63. crackerjack/mcp/__init__.py +0 -0
  64. crackerjack/mcp/cache.py +336 -0
  65. crackerjack/mcp/client_runner.py +104 -0
  66. crackerjack/mcp/context.py +615 -0
  67. crackerjack/mcp/dashboard.py +636 -0
  68. crackerjack/mcp/enhanced_progress_monitor.py +479 -0
  69. crackerjack/mcp/file_monitor.py +336 -0
  70. crackerjack/mcp/progress_components.py +569 -0
  71. crackerjack/mcp/progress_monitor.py +949 -0
  72. crackerjack/mcp/rate_limiter.py +332 -0
  73. crackerjack/mcp/server.py +22 -0
  74. crackerjack/mcp/server_core.py +244 -0
  75. crackerjack/mcp/service_watchdog.py +501 -0
  76. crackerjack/mcp/state.py +395 -0
  77. crackerjack/mcp/task_manager.py +257 -0
  78. crackerjack/mcp/tools/__init__.py +17 -0
  79. crackerjack/mcp/tools/core_tools.py +249 -0
  80. crackerjack/mcp/tools/error_analyzer.py +308 -0
  81. crackerjack/mcp/tools/execution_tools.py +370 -0
  82. crackerjack/mcp/tools/execution_tools_backup.py +1097 -0
  83. crackerjack/mcp/tools/intelligence_tool_registry.py +80 -0
  84. crackerjack/mcp/tools/intelligence_tools.py +314 -0
  85. crackerjack/mcp/tools/monitoring_tools.py +502 -0
  86. crackerjack/mcp/tools/proactive_tools.py +384 -0
  87. crackerjack/mcp/tools/progress_tools.py +141 -0
  88. crackerjack/mcp/tools/utility_tools.py +341 -0
  89. crackerjack/mcp/tools/workflow_executor.py +360 -0
  90. crackerjack/mcp/websocket/__init__.py +14 -0
  91. crackerjack/mcp/websocket/app.py +39 -0
  92. crackerjack/mcp/websocket/endpoints.py +559 -0
  93. crackerjack/mcp/websocket/jobs.py +253 -0
  94. crackerjack/mcp/websocket/server.py +116 -0
  95. crackerjack/mcp/websocket/websocket_handler.py +78 -0
  96. crackerjack/mcp/websocket_server.py +10 -0
  97. crackerjack/models/__init__.py +31 -0
  98. crackerjack/models/config.py +93 -0
  99. crackerjack/models/config_adapter.py +230 -0
  100. crackerjack/models/protocols.py +118 -0
  101. crackerjack/models/task.py +154 -0
  102. crackerjack/monitoring/ai_agent_watchdog.py +450 -0
  103. crackerjack/monitoring/regression_prevention.py +638 -0
  104. crackerjack/orchestration/__init__.py +0 -0
  105. crackerjack/orchestration/advanced_orchestrator.py +970 -0
  106. crackerjack/orchestration/execution_strategies.py +341 -0
  107. crackerjack/orchestration/test_progress_streamer.py +636 -0
  108. crackerjack/plugins/__init__.py +15 -0
  109. crackerjack/plugins/base.py +200 -0
  110. crackerjack/plugins/hooks.py +246 -0
  111. crackerjack/plugins/loader.py +335 -0
  112. crackerjack/plugins/managers.py +259 -0
  113. crackerjack/py313.py +8 -3
  114. crackerjack/services/__init__.py +22 -0
  115. crackerjack/services/cache.py +314 -0
  116. crackerjack/services/config.py +347 -0
  117. crackerjack/services/config_integrity.py +99 -0
  118. crackerjack/services/contextual_ai_assistant.py +516 -0
  119. crackerjack/services/coverage_ratchet.py +347 -0
  120. crackerjack/services/debug.py +736 -0
  121. crackerjack/services/dependency_monitor.py +617 -0
  122. crackerjack/services/enhanced_filesystem.py +439 -0
  123. crackerjack/services/file_hasher.py +151 -0
  124. crackerjack/services/filesystem.py +395 -0
  125. crackerjack/services/git.py +165 -0
  126. crackerjack/services/health_metrics.py +611 -0
  127. crackerjack/services/initialization.py +847 -0
  128. crackerjack/services/log_manager.py +286 -0
  129. crackerjack/services/logging.py +174 -0
  130. crackerjack/services/metrics.py +578 -0
  131. crackerjack/services/pattern_cache.py +362 -0
  132. crackerjack/services/pattern_detector.py +515 -0
  133. crackerjack/services/performance_benchmarks.py +653 -0
  134. crackerjack/services/security.py +163 -0
  135. crackerjack/services/server_manager.py +234 -0
  136. crackerjack/services/smart_scheduling.py +144 -0
  137. crackerjack/services/tool_version_service.py +61 -0
  138. crackerjack/services/unified_config.py +437 -0
  139. crackerjack/services/version_checker.py +248 -0
  140. crackerjack/slash_commands/__init__.py +14 -0
  141. crackerjack/slash_commands/init.md +122 -0
  142. crackerjack/slash_commands/run.md +163 -0
  143. crackerjack/slash_commands/status.md +127 -0
  144. crackerjack-0.31.4.dist-info/METADATA +742 -0
  145. crackerjack-0.31.4.dist-info/RECORD +148 -0
  146. crackerjack-0.31.4.dist-info/entry_points.txt +2 -0
  147. crackerjack/.gitignore +0 -34
  148. crackerjack/.libcst.codemod.yaml +0 -18
  149. crackerjack/.pdm.toml +0 -1
  150. crackerjack/.pre-commit-config-ai.yaml +0 -149
  151. crackerjack/.pre-commit-config-fast.yaml +0 -69
  152. crackerjack/.pre-commit-config.yaml +0 -114
  153. crackerjack/crackerjack.py +0 -4140
  154. crackerjack/pyproject.toml +0 -285
  155. crackerjack-0.29.0.dist-info/METADATA +0 -1289
  156. crackerjack-0.29.0.dist-info/RECORD +0 -17
  157. {crackerjack-0.29.0.dist-info → crackerjack-0.31.4.dist-info}/WHEEL +0 -0
  158. {crackerjack-0.29.0.dist-info → crackerjack-0.31.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,498 @@
1
+ import re
2
+ import subprocess
3
+ from contextlib import suppress
4
+ from datetime import datetime
5
+ from pathlib import Path
6
+
7
+ from .base import (
8
+ FixResult,
9
+ Issue,
10
+ IssueType,
11
+ SubAgent,
12
+ agent_registry,
13
+ )
14
+
15
+
16
+ class DocumentationAgent(SubAgent):
17
+ """Agent specialized in maintaining documentation consistency and changelog updates."""
18
+
19
+ def get_supported_types(self) -> set[IssueType]:
20
+ return {IssueType.DOCUMENTATION}
21
+
22
+ async def can_handle(self, issue: Issue) -> float:
23
+ if issue.type == IssueType.DOCUMENTATION:
24
+ return 0.8
25
+ return 0.0
26
+
27
+ async def analyze_and_fix(self, issue: Issue) -> FixResult:
28
+ self.log(f"Analyzing documentation issue: {issue.message}")
29
+
30
+ try:
31
+ # Detect what type of documentation update is needed
32
+ if "changelog" in issue.message.lower():
33
+ return await self._update_changelog(issue)
34
+ if (
35
+ "agent count" in issue.message.lower()
36
+ or "consistency" in issue.message.lower()
37
+ ):
38
+ return await self._fix_documentation_consistency(issue)
39
+ if "api" in issue.message.lower() or "readme" in issue.message.lower():
40
+ return await self._update_api_documentation(issue)
41
+ return await self._general_documentation_update(issue)
42
+
43
+ except Exception as e:
44
+ return FixResult(
45
+ success=False,
46
+ confidence=0.0,
47
+ remaining_issues=[f"Error processing documentation: {e}"],
48
+ )
49
+
50
+ async def _update_changelog(self, issue: Issue) -> FixResult:
51
+ """Update CHANGELOG.md with recent changes."""
52
+ self.log("Updating changelog with recent changes")
53
+
54
+ changelog_path = Path("CHANGELOG.md")
55
+
56
+ # Get recent commits since last version tag
57
+ recent_changes = self._get_recent_changes()
58
+
59
+ if not recent_changes:
60
+ return FixResult(
61
+ success=True,
62
+ confidence=0.7,
63
+ recommendations=["No recent changes to add to changelog"],
64
+ )
65
+
66
+ # Generate changelog entry
67
+ changelog_entry = self._generate_changelog_entry(recent_changes)
68
+
69
+ # Update or create changelog
70
+ if changelog_path.exists():
71
+ content = self.context.get_file_content(changelog_path)
72
+ if content is None:
73
+ return FixResult(
74
+ success=False,
75
+ confidence=0.0,
76
+ remaining_issues=[f"Failed to read {changelog_path}"],
77
+ )
78
+ updated_content = self._insert_changelog_entry(content, changelog_entry)
79
+ else:
80
+ updated_content = self._create_initial_changelog(changelog_entry)
81
+
82
+ success = self.context.write_file_content(changelog_path, updated_content)
83
+
84
+ if success:
85
+ return FixResult(
86
+ success=True,
87
+ confidence=0.9,
88
+ fixes_applied=[
89
+ f"Updated CHANGELOG.md with {len(recent_changes)} recent changes",
90
+ ],
91
+ files_modified=[str(changelog_path)],
92
+ )
93
+
94
+ return FixResult(
95
+ success=False,
96
+ confidence=0.0,
97
+ remaining_issues=["Failed to write changelog updates"],
98
+ )
99
+
100
+ async def _fix_documentation_consistency(self, issue: Issue) -> FixResult:
101
+ """Fix consistency issues across documentation files."""
102
+ self.log("Checking documentation consistency")
103
+
104
+ # Find all markdown files
105
+ md_files = list(Path().glob("*.md")) + list(Path("docs").glob("*.md"))
106
+
107
+ # Check agent count consistency
108
+ agent_count_issues = self._check_agent_count_consistency(md_files)
109
+
110
+ files_modified: list[str] = []
111
+ fixes_applied: list[str] = []
112
+
113
+ for file_path, current_count, expected_count in agent_count_issues:
114
+ content = self.context.get_file_content(file_path)
115
+ if content:
116
+ # Fix agent count references
117
+ updated_content = self._fix_agent_count_references(
118
+ content,
119
+ current_count,
120
+ expected_count,
121
+ )
122
+ if updated_content != content:
123
+ success = self.context.write_file_content(
124
+ file_path,
125
+ updated_content,
126
+ )
127
+ if success:
128
+ files_modified.append(str(file_path))
129
+ fixes_applied.append(f"Updated agent count in {file_path.name}")
130
+
131
+ if files_modified:
132
+ return FixResult(
133
+ success=True,
134
+ confidence=0.85,
135
+ fixes_applied=fixes_applied,
136
+ files_modified=files_modified,
137
+ )
138
+
139
+ return FixResult(
140
+ success=True,
141
+ confidence=0.8,
142
+ recommendations=["Documentation is already consistent"],
143
+ )
144
+
145
+ async def _update_api_documentation(self, issue: Issue) -> FixResult:
146
+ """Update API documentation when public interfaces change."""
147
+ self.log("Updating API documentation")
148
+
149
+ # Check for API changes by analyzing recent modifications
150
+ api_changes = self._detect_api_changes()
151
+
152
+ if not api_changes:
153
+ return FixResult(
154
+ success=True,
155
+ confidence=0.7,
156
+ recommendations=[
157
+ "No API changes detected requiring documentation updates",
158
+ ],
159
+ )
160
+
161
+ # Update README examples
162
+ readme_path = Path("README.md")
163
+ if readme_path.exists():
164
+ content = self.context.get_file_content(readme_path)
165
+ if content is None:
166
+ return FixResult(
167
+ success=False,
168
+ confidence=0.0,
169
+ remaining_issues=[f"Failed to read {readme_path}"],
170
+ )
171
+ updated_content = self._update_readme_examples(content, api_changes)
172
+
173
+ if updated_content != content:
174
+ success = self.context.write_file_content(readme_path, updated_content)
175
+ if success:
176
+ return FixResult(
177
+ success=True,
178
+ confidence=0.8,
179
+ fixes_applied=["Updated README.md examples for API changes"],
180
+ files_modified=[str(readme_path)],
181
+ )
182
+
183
+ return FixResult(
184
+ success=False,
185
+ confidence=0.5,
186
+ remaining_issues=["Could not update API documentation"],
187
+ recommendations=["Manual review of API documentation may be needed"],
188
+ )
189
+
190
+ async def _general_documentation_update(self, issue: Issue) -> FixResult:
191
+ """Handle general documentation updates."""
192
+ self.log("Performing general documentation update")
193
+
194
+ # Add review comments for manual review
195
+
196
+ return FixResult(
197
+ success=True,
198
+ confidence=0.6,
199
+ recommendations=[
200
+ f"Documentation issue identified: {issue.message}",
201
+ "Manual review recommended for optimal documentation updates",
202
+ "Consider adding specific patterns to DocumentationAgent",
203
+ ],
204
+ )
205
+
206
+ def _get_recent_changes(self) -> list[dict[str, str]]:
207
+ """Get recent git commits since last version tag."""
208
+ try:
209
+ commit_range = self._get_commit_range()
210
+ if not commit_range:
211
+ return []
212
+
213
+ commit_messages = self._get_commit_messages(commit_range)
214
+ return self._parse_commit_messages(commit_messages)
215
+
216
+ except Exception:
217
+ return []
218
+
219
+ def _get_commit_range(self) -> str:
220
+ """Determine the commit range for changelog generation."""
221
+ result = subprocess.run(
222
+ ["git", "describe", "--tags", "--abbrev=0"],
223
+ capture_output=True,
224
+ text=True,
225
+ check=False,
226
+ )
227
+
228
+ if result.returncode == 0:
229
+ last_tag = result.stdout.strip()
230
+ return f"{last_tag}..HEAD"
231
+
232
+ # No tags found, get last 10 commits
233
+ return "-10"
234
+
235
+ def _get_commit_messages(self, commit_range: str) -> str:
236
+ """Get formatted commit messages for the given range."""
237
+ result = subprocess.run(
238
+ ["git", "log", commit_range, "--pretty=format:%s|%h|%an"],
239
+ capture_output=True,
240
+ text=True,
241
+ check=False,
242
+ )
243
+
244
+ return result.stdout.strip() if result.returncode == 0 else ""
245
+
246
+ def _parse_commit_messages(self, commit_output: str) -> list[dict[str, str]]:
247
+ """Parse git log output into structured change information."""
248
+ changes: list[dict[str, str]] = []
249
+
250
+ for line in commit_output.split("\n"):
251
+ if line:
252
+ parts = line.split("|")
253
+ if len(parts) >= 2:
254
+ change_info: dict[str, str] = {
255
+ "message": parts[0],
256
+ "hash": parts[1],
257
+ "author": parts[2] if len(parts) > 2 else "Unknown",
258
+ }
259
+ changes.append(change_info)
260
+
261
+ return changes
262
+
263
+ def _generate_changelog_entry(self, changes: list[dict[str, str]]) -> str:
264
+ """Generate a formatted changelog entry."""
265
+ date_str = datetime.now().strftime("%Y-%m-%d")
266
+ entry_lines = [f"## [Unreleased] - {date_str}", ""]
267
+
268
+ categorized_changes = self._categorize_changes(changes)
269
+ self._add_categorized_changes_to_entry(entry_lines, categorized_changes)
270
+
271
+ return "\n".join(entry_lines)
272
+
273
+ def _categorize_changes(
274
+ self,
275
+ changes: list[dict[str, str]],
276
+ ) -> dict[str, list[str]]:
277
+ """Categorize changes by type."""
278
+ categories: dict[str, list[str]] = {
279
+ "features": [],
280
+ "fixes": [],
281
+ "refactors": [],
282
+ "other": [],
283
+ }
284
+
285
+ for change in changes:
286
+ message = change["message"]
287
+ category = self._get_change_category(message)
288
+ categories[category].append(message)
289
+
290
+ return categories
291
+
292
+ def _get_change_category(self, message: str) -> str:
293
+ """Determine the category for a change message."""
294
+ if message.startswith(("feat:", "feature:")):
295
+ return "features"
296
+ if message.startswith("fix:"):
297
+ return "fixes"
298
+ if message.startswith(("refactor:", "refact:")):
299
+ return "refactors"
300
+ return "other"
301
+
302
+ def _add_categorized_changes_to_entry(
303
+ self,
304
+ entry_lines: list[str],
305
+ categories: dict[str, list[str]],
306
+ ) -> None:
307
+ """Add categorized changes to the entry lines."""
308
+ section_mappings = {
309
+ "features": "### Added",
310
+ "fixes": "### Fixed",
311
+ "refactors": "### Changed",
312
+ "other": "### Other",
313
+ }
314
+
315
+ for category, section_title in section_mappings.items():
316
+ items = categories[category]
317
+ if items:
318
+ self._add_section_to_entry(entry_lines, section_title, items)
319
+
320
+ def _add_section_to_entry(
321
+ self,
322
+ entry_lines: list[str],
323
+ section_title: str,
324
+ items: list[str],
325
+ ) -> None:
326
+ """Add a section with items to the entry lines."""
327
+ entry_lines.append(section_title)
328
+ for item in items:
329
+ entry_lines.append(f"- {item}")
330
+ entry_lines.append("")
331
+
332
+ def _insert_changelog_entry(self, content: str, entry: str) -> str:
333
+ """Insert new changelog entry at the top."""
334
+ lines = content.split("\n")
335
+
336
+ # Find where to insert (after title and before first entry)
337
+ insert_index = 0
338
+ for i, line in enumerate(lines):
339
+ if line.startswith(("# ", "## ")):
340
+ if i > 0: # Skip the main title
341
+ insert_index = i
342
+ break
343
+
344
+ # Insert the new entry
345
+ new_lines = (
346
+ lines[:insert_index] + entry.split("\n") + [""] + lines[insert_index:]
347
+ )
348
+ return "\n".join(new_lines)
349
+
350
+ def _create_initial_changelog(self, entry: str) -> str:
351
+ """Create initial CHANGELOG.md with first entry."""
352
+ return f"""# Changelog
353
+
354
+ All notable changes to this project will be documented in this file.
355
+
356
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
357
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
358
+
359
+ {entry}
360
+ """
361
+
362
+ def _check_agent_count_consistency(
363
+ self,
364
+ md_files: list[Path],
365
+ ) -> list[tuple[Path, int, int]]:
366
+ """Check for inconsistent agent count references across documentation."""
367
+ expected_count = 9 # Current total with DocumentationAgent
368
+ issues: list[tuple[Path, int, int]] = []
369
+ patterns = self._get_agent_count_patterns()
370
+
371
+ for file_path in md_files:
372
+ issue = self._check_file_agent_count(file_path, patterns, expected_count)
373
+ if issue:
374
+ issues.append(issue)
375
+
376
+ return issues
377
+
378
+ def _get_agent_count_patterns(self) -> list[str]:
379
+ """Get regex patterns for detecting agent count references."""
380
+ return [
381
+ r"(\d+)\s+agents",
382
+ r"(\d+)\s+specialized\s+agents",
383
+ r'total_agents["\']:\s*(\d+)',
384
+ r"(\d+)\s+sub-agents",
385
+ ]
386
+
387
+ def _check_file_agent_count(
388
+ self,
389
+ file_path: Path,
390
+ patterns: list[str],
391
+ expected_count: int,
392
+ ) -> tuple[Path, int, int] | None:
393
+ """Check a single file for agent count inconsistencies."""
394
+ with suppress(Exception):
395
+ content = self.context.get_file_content(file_path)
396
+ if not content:
397
+ return None
398
+
399
+ for pattern in patterns:
400
+ matches = re.findall(pattern, content, re.IGNORECASE)
401
+ for match in matches:
402
+ count = int(match)
403
+ if (
404
+ count != expected_count and count > 4
405
+ ): # Filter out unrelated numbers
406
+ return (file_path, count, expected_count)
407
+
408
+ return None
409
+
410
+ def _fix_agent_count_references(
411
+ self,
412
+ content: str,
413
+ current_count: int,
414
+ expected_count: int,
415
+ ) -> str:
416
+ """Fix agent count references in documentation."""
417
+ # Replace various agent count patterns
418
+ patterns_replacements = [
419
+ (rf"\b{current_count}\s+agents\b", f"{expected_count} agents"),
420
+ (
421
+ rf"\b{current_count}\s+specialized\s+agents\b",
422
+ f"{expected_count} specialized agents",
423
+ ),
424
+ (
425
+ rf'total_agents["\']:\s*{current_count}',
426
+ f'total_agents": {expected_count}',
427
+ ),
428
+ (rf"\b{current_count}\s+sub-agents\b", f"{expected_count} sub-agents"),
429
+ ]
430
+
431
+ updated_content = content
432
+ for pattern, replacement in patterns_replacements:
433
+ updated_content = re.sub(
434
+ pattern,
435
+ replacement,
436
+ updated_content,
437
+ flags=re.IGNORECASE,
438
+ )
439
+
440
+ return updated_content
441
+
442
+ def _detect_api_changes(self) -> list[dict[str, str]]:
443
+ """Detect recent API changes that might affect documentation."""
444
+ # This is a simplified implementation - in practice would use AST analysis
445
+ try:
446
+ result = subprocess.run(
447
+ ["git", "diff", "--name-only", "HEAD~5..HEAD", "*.py"],
448
+ capture_output=True,
449
+ text=True,
450
+ check=False,
451
+ )
452
+
453
+ if result.returncode != 0:
454
+ return []
455
+
456
+ changed_files = result.stdout.strip().split("\n")
457
+ api_changes: list[dict[str, str]] = []
458
+
459
+ for file_path in changed_files:
460
+ if file_path and (
461
+ "api" in file_path.lower() or "__init__" in file_path
462
+ ):
463
+ change_info: dict[str, str] = {
464
+ "file": file_path,
465
+ "type": "potential_api_change",
466
+ }
467
+ api_changes.append(change_info)
468
+
469
+ return api_changes
470
+
471
+ except Exception:
472
+ return []
473
+
474
+ def _update_readme_examples(
475
+ self,
476
+ content: str,
477
+ api_changes: list[dict[str, str]],
478
+ ) -> str:
479
+ """Update README examples based on API changes."""
480
+ # This is a placeholder - real implementation would parse and update code examples
481
+ # For now, just add a comment noting API changes
482
+ if api_changes and "TODO: Update examples" not in content:
483
+ # Add a note about updating examples
484
+ lines = content.split("\n")
485
+ # Insert near the top after the title
486
+ for i, line in enumerate(lines):
487
+ if line.startswith("# ") and i < len(lines) - 1:
488
+ lines.insert(
489
+ i + 2,
490
+ "<!-- TODO: Update examples after recent API changes -->",
491
+ )
492
+ break
493
+ return "\n".join(lines)
494
+
495
+ return content
496
+
497
+
498
+ agent_registry.register(DocumentationAgent)