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.
- crackerjack/CLAUDE.md +1005 -0
- crackerjack/RULES.md +380 -0
- crackerjack/__init__.py +42 -13
- crackerjack/__main__.py +225 -253
- crackerjack/agents/__init__.py +41 -0
- crackerjack/agents/architect_agent.py +281 -0
- crackerjack/agents/base.py +169 -0
- crackerjack/agents/coordinator.py +512 -0
- crackerjack/agents/documentation_agent.py +498 -0
- crackerjack/agents/dry_agent.py +388 -0
- crackerjack/agents/formatting_agent.py +245 -0
- crackerjack/agents/import_optimization_agent.py +281 -0
- crackerjack/agents/performance_agent.py +669 -0
- crackerjack/agents/proactive_agent.py +104 -0
- crackerjack/agents/refactoring_agent.py +788 -0
- crackerjack/agents/security_agent.py +529 -0
- crackerjack/agents/test_creation_agent.py +652 -0
- crackerjack/agents/test_specialist_agent.py +486 -0
- crackerjack/agents/tracker.py +212 -0
- crackerjack/api.py +560 -0
- crackerjack/cli/__init__.py +24 -0
- crackerjack/cli/facade.py +104 -0
- crackerjack/cli/handlers.py +267 -0
- crackerjack/cli/interactive.py +471 -0
- crackerjack/cli/options.py +401 -0
- crackerjack/cli/utils.py +18 -0
- crackerjack/code_cleaner.py +670 -0
- crackerjack/config/__init__.py +19 -0
- crackerjack/config/hooks.py +218 -0
- crackerjack/core/__init__.py +0 -0
- crackerjack/core/async_workflow_orchestrator.py +406 -0
- crackerjack/core/autofix_coordinator.py +200 -0
- crackerjack/core/container.py +104 -0
- crackerjack/core/enhanced_container.py +542 -0
- crackerjack/core/performance.py +243 -0
- crackerjack/core/phase_coordinator.py +561 -0
- crackerjack/core/proactive_workflow.py +316 -0
- crackerjack/core/session_coordinator.py +289 -0
- crackerjack/core/workflow_orchestrator.py +640 -0
- crackerjack/dynamic_config.py +577 -0
- crackerjack/errors.py +263 -41
- crackerjack/executors/__init__.py +11 -0
- crackerjack/executors/async_hook_executor.py +431 -0
- crackerjack/executors/cached_hook_executor.py +242 -0
- crackerjack/executors/hook_executor.py +345 -0
- crackerjack/executors/individual_hook_executor.py +669 -0
- crackerjack/intelligence/__init__.py +44 -0
- crackerjack/intelligence/adaptive_learning.py +751 -0
- crackerjack/intelligence/agent_orchestrator.py +551 -0
- crackerjack/intelligence/agent_registry.py +414 -0
- crackerjack/intelligence/agent_selector.py +502 -0
- crackerjack/intelligence/integration.py +290 -0
- crackerjack/interactive.py +576 -315
- crackerjack/managers/__init__.py +11 -0
- crackerjack/managers/async_hook_manager.py +135 -0
- crackerjack/managers/hook_manager.py +137 -0
- crackerjack/managers/publish_manager.py +411 -0
- crackerjack/managers/test_command_builder.py +151 -0
- crackerjack/managers/test_executor.py +435 -0
- crackerjack/managers/test_manager.py +258 -0
- crackerjack/managers/test_manager_backup.py +1124 -0
- crackerjack/managers/test_progress.py +144 -0
- crackerjack/mcp/__init__.py +0 -0
- crackerjack/mcp/cache.py +336 -0
- crackerjack/mcp/client_runner.py +104 -0
- crackerjack/mcp/context.py +615 -0
- crackerjack/mcp/dashboard.py +636 -0
- crackerjack/mcp/enhanced_progress_monitor.py +479 -0
- crackerjack/mcp/file_monitor.py +336 -0
- crackerjack/mcp/progress_components.py +569 -0
- crackerjack/mcp/progress_monitor.py +949 -0
- crackerjack/mcp/rate_limiter.py +332 -0
- crackerjack/mcp/server.py +22 -0
- crackerjack/mcp/server_core.py +244 -0
- crackerjack/mcp/service_watchdog.py +501 -0
- crackerjack/mcp/state.py +395 -0
- crackerjack/mcp/task_manager.py +257 -0
- crackerjack/mcp/tools/__init__.py +17 -0
- crackerjack/mcp/tools/core_tools.py +249 -0
- crackerjack/mcp/tools/error_analyzer.py +308 -0
- crackerjack/mcp/tools/execution_tools.py +370 -0
- crackerjack/mcp/tools/execution_tools_backup.py +1097 -0
- crackerjack/mcp/tools/intelligence_tool_registry.py +80 -0
- crackerjack/mcp/tools/intelligence_tools.py +314 -0
- crackerjack/mcp/tools/monitoring_tools.py +502 -0
- crackerjack/mcp/tools/proactive_tools.py +384 -0
- crackerjack/mcp/tools/progress_tools.py +141 -0
- crackerjack/mcp/tools/utility_tools.py +341 -0
- crackerjack/mcp/tools/workflow_executor.py +360 -0
- crackerjack/mcp/websocket/__init__.py +14 -0
- crackerjack/mcp/websocket/app.py +39 -0
- crackerjack/mcp/websocket/endpoints.py +559 -0
- crackerjack/mcp/websocket/jobs.py +253 -0
- crackerjack/mcp/websocket/server.py +116 -0
- crackerjack/mcp/websocket/websocket_handler.py +78 -0
- crackerjack/mcp/websocket_server.py +10 -0
- crackerjack/models/__init__.py +31 -0
- crackerjack/models/config.py +93 -0
- crackerjack/models/config_adapter.py +230 -0
- crackerjack/models/protocols.py +118 -0
- crackerjack/models/task.py +154 -0
- crackerjack/monitoring/ai_agent_watchdog.py +450 -0
- crackerjack/monitoring/regression_prevention.py +638 -0
- crackerjack/orchestration/__init__.py +0 -0
- crackerjack/orchestration/advanced_orchestrator.py +970 -0
- crackerjack/orchestration/execution_strategies.py +341 -0
- crackerjack/orchestration/test_progress_streamer.py +636 -0
- crackerjack/plugins/__init__.py +15 -0
- crackerjack/plugins/base.py +200 -0
- crackerjack/plugins/hooks.py +246 -0
- crackerjack/plugins/loader.py +335 -0
- crackerjack/plugins/managers.py +259 -0
- crackerjack/py313.py +8 -3
- crackerjack/services/__init__.py +22 -0
- crackerjack/services/cache.py +314 -0
- crackerjack/services/config.py +347 -0
- crackerjack/services/config_integrity.py +99 -0
- crackerjack/services/contextual_ai_assistant.py +516 -0
- crackerjack/services/coverage_ratchet.py +347 -0
- crackerjack/services/debug.py +736 -0
- crackerjack/services/dependency_monitor.py +617 -0
- crackerjack/services/enhanced_filesystem.py +439 -0
- crackerjack/services/file_hasher.py +151 -0
- crackerjack/services/filesystem.py +395 -0
- crackerjack/services/git.py +165 -0
- crackerjack/services/health_metrics.py +611 -0
- crackerjack/services/initialization.py +847 -0
- crackerjack/services/log_manager.py +286 -0
- crackerjack/services/logging.py +174 -0
- crackerjack/services/metrics.py +578 -0
- crackerjack/services/pattern_cache.py +362 -0
- crackerjack/services/pattern_detector.py +515 -0
- crackerjack/services/performance_benchmarks.py +653 -0
- crackerjack/services/security.py +163 -0
- crackerjack/services/server_manager.py +234 -0
- crackerjack/services/smart_scheduling.py +144 -0
- crackerjack/services/tool_version_service.py +61 -0
- crackerjack/services/unified_config.py +437 -0
- crackerjack/services/version_checker.py +248 -0
- crackerjack/slash_commands/__init__.py +14 -0
- crackerjack/slash_commands/init.md +122 -0
- crackerjack/slash_commands/run.md +163 -0
- crackerjack/slash_commands/status.md +127 -0
- crackerjack-0.31.4.dist-info/METADATA +742 -0
- crackerjack-0.31.4.dist-info/RECORD +148 -0
- crackerjack-0.31.4.dist-info/entry_points.txt +2 -0
- crackerjack/.gitignore +0 -34
- crackerjack/.libcst.codemod.yaml +0 -18
- crackerjack/.pdm.toml +0 -1
- crackerjack/.pre-commit-config-ai.yaml +0 -149
- crackerjack/.pre-commit-config-fast.yaml +0 -69
- crackerjack/.pre-commit-config.yaml +0 -114
- crackerjack/crackerjack.py +0 -4140
- crackerjack/pyproject.toml +0 -285
- crackerjack-0.29.0.dist-info/METADATA +0 -1289
- crackerjack-0.29.0.dist-info/RECORD +0 -17
- {crackerjack-0.29.0.dist-info → crackerjack-0.31.4.dist-info}/WHEEL +0 -0
- {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)
|