deepagents-printshop 0.1.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.
- agents/content_editor/__init__.py +1 -0
- agents/content_editor/agent.py +279 -0
- agents/content_editor/content_reviewer.py +327 -0
- agents/content_editor/versioned_agent.py +455 -0
- agents/latex_specialist/__init__.py +1 -0
- agents/latex_specialist/agent.py +531 -0
- agents/latex_specialist/latex_analyzer.py +510 -0
- agents/latex_specialist/latex_optimizer.py +1192 -0
- agents/qa_orchestrator/__init__.py +1 -0
- agents/qa_orchestrator/agent.py +603 -0
- agents/qa_orchestrator/langgraph_workflow.py +733 -0
- agents/qa_orchestrator/pipeline_types.py +72 -0
- agents/qa_orchestrator/quality_gates.py +495 -0
- agents/qa_orchestrator/workflow_coordinator.py +139 -0
- agents/research_agent/__init__.py +1 -0
- agents/research_agent/agent.py +258 -0
- agents/research_agent/llm_report_generator.py +1023 -0
- agents/research_agent/report_generator.py +536 -0
- agents/visual_qa/__init__.py +1 -0
- agents/visual_qa/agent.py +410 -0
- deepagents_printshop-0.1.0.dist-info/METADATA +744 -0
- deepagents_printshop-0.1.0.dist-info/RECORD +37 -0
- deepagents_printshop-0.1.0.dist-info/WHEEL +4 -0
- deepagents_printshop-0.1.0.dist-info/entry_points.txt +2 -0
- deepagents_printshop-0.1.0.dist-info/licenses/LICENSE +86 -0
- tools/__init__.py +1 -0
- tools/change_tracker.py +419 -0
- tools/content_type_loader.py +171 -0
- tools/graph_generator.py +281 -0
- tools/latex_generator.py +374 -0
- tools/llm_latex_generator.py +678 -0
- tools/magazine_layout.py +462 -0
- tools/pattern_injector.py +250 -0
- tools/pattern_learner.py +477 -0
- tools/pdf_compiler.py +386 -0
- tools/version_manager.py +346 -0
- tools/visual_qa.py +799 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
agents/content_editor/__init__.py,sha256=ruMzTy6J3tEQ2ylke1RUNlb4HD7bC2oeRjqQ0DoEAFg,52
|
|
2
|
+
agents/content_editor/agent.py,sha256=o2d-eee8q9qpeOVkaCMxhd6sv8Vu1yG-VVEu4ZjUxA4,9669
|
|
3
|
+
agents/content_editor/content_reviewer.py,sha256=4Z57bONlPP53zS8ob6mFzQ0HUi6nPFy5E5Y83YlM1Rk,11585
|
|
4
|
+
agents/content_editor/versioned_agent.py,sha256=F6AMM1y4W2_i9az_aY2VyBREpdx5wfZ9UxIYoG6D_3k,17415
|
|
5
|
+
agents/latex_specialist/__init__.py,sha256=XtnLmA4V157Y-bKFAl0XpCowvEzZ0RDXVEfpY_qiYZs,54
|
|
6
|
+
agents/latex_specialist/agent.py,sha256=wAtpGkDjLTaFK8l7kZg3VugjlqGJWNevVTNv6XNgHGU,19271
|
|
7
|
+
agents/latex_specialist/latex_analyzer.py,sha256=a7MZYzblcdDj5igQAWFp9G7pmAwAb4phsh1RA0aveLI,19860
|
|
8
|
+
agents/latex_specialist/latex_optimizer.py,sha256=birJDUngwxP4M0EoLpTQ2aqOVNfuWANxAppINynoE0Q,52405
|
|
9
|
+
agents/qa_orchestrator/__init__.py,sha256=gQtYVxxyQ5QjMFhKSVfAkBiYn1rgK172bf75Ty1-VJA,53
|
|
10
|
+
agents/qa_orchestrator/agent.py,sha256=AE4Fr15lef5pNaPugtXJx2ryAKpRRr7wRMwt6VnEuv0,23753
|
|
11
|
+
agents/qa_orchestrator/langgraph_workflow.py,sha256=0r-fiNCXV90hvrviDOPBUGX9eyJMU5rNJSQvw_OZakg,31186
|
|
12
|
+
agents/qa_orchestrator/pipeline_types.py,sha256=aPBSDQVJrOTGy5-JMeWWzYnW70td_UWuEe2D55w3NyA,2448
|
|
13
|
+
agents/qa_orchestrator/quality_gates.py,sha256=sUpI9ZHtizoNfHVukopqdkTokUBpCSod5vxpsGfXRew,20191
|
|
14
|
+
agents/qa_orchestrator/workflow_coordinator.py,sha256=mcUwkhmEC-6I-myyERJWoAhaHpXbsxXtPQMSqI2yZWg,6067
|
|
15
|
+
agents/research_agent/__init__.py,sha256=BZ_tHXe9oLT6mKcOMpAL3_0SWUQETwuaz0PEADwWhkE,52
|
|
16
|
+
agents/research_agent/agent.py,sha256=LjzM6qKMXRw6f_GH07rWk4Yh2sIpbEIKaMIZt1eTqWE,8259
|
|
17
|
+
agents/research_agent/llm_report_generator.py,sha256=GKaiDj2Q8f8v2-yp9wY-AQbQNs_6W6B9wJUbmI8f600,40938
|
|
18
|
+
agents/research_agent/report_generator.py,sha256=COAaEP-2Jh8viPogC-rWRmRNDTcIFvi_wDZtFn0TL8c,21828
|
|
19
|
+
agents/visual_qa/__init__.py,sha256=27OkVYIjeQ7U9U-2p4EKbBiF-XJ5QfavF1FizXIefXk,62
|
|
20
|
+
agents/visual_qa/agent.py,sha256=VJsi9lsIrLKFaqbz19Zy1eBtooYqc_JpeDgi7HIM1P4,17343
|
|
21
|
+
tools/__init__.py,sha256=wO6yiGkr4FhjajzqqyALijIYlSMC928sraJz9vc7h4g,77
|
|
22
|
+
tools/change_tracker.py,sha256=tJLFByt-CiGv6iTAA7Y5J1kk1_w0itXFxncmwb_NHa8,16137
|
|
23
|
+
tools/content_type_loader.py,sha256=Q4AAucdvSYSq9Gq5nQpkyl6gzEgywIzSQT_1H3S_YrE,5653
|
|
24
|
+
tools/graph_generator.py,sha256=rHAwxym3sAF-hSticYVL0_OLJiWlOituRPd0zSurEnI,10581
|
|
25
|
+
tools/latex_generator.py,sha256=p3EwIRS8BotP8P9o2TR7S2ic_AakNln78GXZ95RToOk,12153
|
|
26
|
+
tools/llm_latex_generator.py,sha256=RMIrXTiIBRhB5pDnW8-gWldOiU_g7jjTeDp7g-1I3gs,26947
|
|
27
|
+
tools/magazine_layout.py,sha256=yg0-njXcPrc5_VWCujd_3dzh5CSzvdTtBPhW17Y_3_o,15987
|
|
28
|
+
tools/pattern_injector.py,sha256=SqQ8qczMuP94KlxcQyirsLUURP5yG8deeFg6hAfw7nU,9080
|
|
29
|
+
tools/pattern_learner.py,sha256=5BIyZNOBgy8QzyHEzvodVO1AILJlo78hzDmo1bFo4cI,18659
|
|
30
|
+
tools/pdf_compiler.py,sha256=Bwd9xgMY1eGx_4dQJzAyhn74hd9K5rWwy-WgbYAqp_4,15627
|
|
31
|
+
tools/version_manager.py,sha256=DGU_5zvfNIis6ou4qQVsXsjneYWuqthmLjvvjYUKvBM,11662
|
|
32
|
+
tools/visual_qa.py,sha256=Ice7cyKX9tGUMETyqR_4v6EM5sgEUIOFbQ7ixaQfSkA,31952
|
|
33
|
+
deepagents_printshop-0.1.0.dist-info/METADATA,sha256=pzY4P-p5ONxSRclxXH1XkcDFHgi2XoMFCt5iweMJ4qY,29157
|
|
34
|
+
deepagents_printshop-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
35
|
+
deepagents_printshop-0.1.0.dist-info/entry_points.txt,sha256=tjvBcpdgDdScxX04QEH8gDZqMVHMWAFxNqJK7rHHy3Y,64
|
|
36
|
+
deepagents_printshop-0.1.0.dist-info/licenses/LICENSE,sha256=ZdDLHuPyBGNgj2MEuQmnjhbmgBC84BZK2309Tjj2Po0,3308
|
|
37
|
+
deepagents_printshop-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# DeepAgents PrintShop License
|
|
2
|
+
|
|
3
|
+
## Software License (Apache 2.0)
|
|
4
|
+
|
|
5
|
+
Copyright 2025-2026 DeepAgents PrintShop Contributors
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Attribution Requirement for Generated Works
|
|
22
|
+
|
|
23
|
+
Any documents, publications, or media generated using DeepAgents PrintShop
|
|
24
|
+
must include the following attribution in a reasonably visible location
|
|
25
|
+
(such as in the document's footer, credits page, colophon, or acknowledgments):
|
|
26
|
+
|
|
27
|
+
"Generated with DeepAgents PrintShop (https://github.com/kormco/deepagents-printshop)"
|
|
28
|
+
|
|
29
|
+
or the shorter form:
|
|
30
|
+
|
|
31
|
+
"Powered by DeepAgents PrintShop"
|
|
32
|
+
|
|
33
|
+
This attribution requirement applies to:
|
|
34
|
+
- PDF documents generated by the system
|
|
35
|
+
- LaTeX source files distributed to others
|
|
36
|
+
- Published works derived from PrintShop output
|
|
37
|
+
- Any derivative media products
|
|
38
|
+
|
|
39
|
+
This requirement does NOT apply to:
|
|
40
|
+
- Internal drafts and working documents not distributed publicly
|
|
41
|
+
- Documents where you substantially rewrite the generated content
|
|
42
|
+
- Uses where attribution is technically impractical
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Sample Content License (CC BY-SA 4.0)
|
|
47
|
+
|
|
48
|
+
The sample content in `artifacts/sample_content/` is licensed under the
|
|
49
|
+
Creative Commons Attribution-ShareAlike 4.0 International License.
|
|
50
|
+
|
|
51
|
+
You are free to:
|
|
52
|
+
- **Share** — copy and redistribute the material in any medium or format
|
|
53
|
+
- **Adapt** — remix, transform, and build upon the material for any purpose
|
|
54
|
+
|
|
55
|
+
Under the following terms:
|
|
56
|
+
- **Attribution** — You must give appropriate credit, provide a link to the
|
|
57
|
+
license, and indicate if changes were made.
|
|
58
|
+
- **ShareAlike** — If you remix, transform, or build upon the material, you
|
|
59
|
+
must distribute your contributions under the same license.
|
|
60
|
+
|
|
61
|
+
Full license text: https://creativecommons.org/licenses/by-sa/4.0/legalcode
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Third-Party Content
|
|
66
|
+
|
|
67
|
+
Images in `artifacts/sample_content/images/` are sourced from Pixabay and are
|
|
68
|
+
used under the Pixabay License (https://pixabay.com/service/license-summary/).
|
|
69
|
+
These images are free for commercial use with no attribution required, but
|
|
70
|
+
attribution is appreciated.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Disclaimer
|
|
75
|
+
|
|
76
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
77
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
78
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
79
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
80
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
81
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
82
|
+
SOFTWARE.
|
|
83
|
+
|
|
84
|
+
This software uses third-party AI services (Claude API). Users are responsible
|
|
85
|
+
for compliance with those services' terms of use. Content processed through
|
|
86
|
+
these services is subject to their respective privacy policies.
|
tools/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Custom tools for the DeepAgents PrintShop document generation system."""
|
tools/change_tracker.py
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Change Tracker - Milestone 2
|
|
3
|
+
|
|
4
|
+
Tracks changes between content versions and generates detailed comparison reports.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import difflib
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from typing import Dict, List, Tuple, Optional
|
|
13
|
+
import re
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ChangeTracker:
|
|
17
|
+
"""
|
|
18
|
+
Tracks and analyzes changes between content versions.
|
|
19
|
+
|
|
20
|
+
Features:
|
|
21
|
+
- Generate text diffs between versions
|
|
22
|
+
- Summarize changes with AI analysis
|
|
23
|
+
- Track quality improvements over time
|
|
24
|
+
- Create detailed change reports
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, base_dir: str = "artifacts"):
|
|
28
|
+
"""
|
|
29
|
+
Initialize the change tracker.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
base_dir: Base directory for artifacts
|
|
33
|
+
"""
|
|
34
|
+
self.base_dir = Path(base_dir)
|
|
35
|
+
self.history_dir = self.base_dir / "version_history"
|
|
36
|
+
self.changes_dir = self.history_dir / "changes"
|
|
37
|
+
self.diffs_dir = self.history_dir / "diffs"
|
|
38
|
+
|
|
39
|
+
# Ensure directories exist
|
|
40
|
+
self.changes_dir.mkdir(parents=True, exist_ok=True)
|
|
41
|
+
self.diffs_dir.mkdir(parents=True, exist_ok=True)
|
|
42
|
+
|
|
43
|
+
def generate_diff(self,
|
|
44
|
+
old_content: str,
|
|
45
|
+
new_content: str,
|
|
46
|
+
filename: str = "content") -> Dict:
|
|
47
|
+
"""
|
|
48
|
+
Generate a detailed diff between two text contents.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
old_content: Original content
|
|
52
|
+
new_content: Modified content
|
|
53
|
+
filename: Name of the file being compared
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Dictionary containing diff information
|
|
57
|
+
"""
|
|
58
|
+
# Split content into lines
|
|
59
|
+
old_lines = old_content.splitlines(keepends=True)
|
|
60
|
+
new_lines = new_content.splitlines(keepends=True)
|
|
61
|
+
|
|
62
|
+
# Generate unified diff
|
|
63
|
+
unified_diff = list(difflib.unified_diff(
|
|
64
|
+
old_lines,
|
|
65
|
+
new_lines,
|
|
66
|
+
fromfile=f"{filename} (old)",
|
|
67
|
+
tofile=f"{filename} (new)",
|
|
68
|
+
lineterm=""
|
|
69
|
+
))
|
|
70
|
+
|
|
71
|
+
# Generate HTML diff
|
|
72
|
+
html_diff = difflib.HtmlDiff()
|
|
73
|
+
html_table = html_diff.make_table(
|
|
74
|
+
old_lines,
|
|
75
|
+
new_lines,
|
|
76
|
+
fromdesc=f"{filename} (old)",
|
|
77
|
+
todesc=f"{filename} (new)"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Calculate statistics
|
|
81
|
+
differ = difflib.SequenceMatcher(None, old_content, new_content)
|
|
82
|
+
similarity_ratio = differ.ratio()
|
|
83
|
+
|
|
84
|
+
# Count changes
|
|
85
|
+
additions = sum(1 for line in unified_diff if line.startswith('+') and not line.startswith('+++'))
|
|
86
|
+
deletions = sum(1 for line in unified_diff if line.startswith('-') and not line.startswith('---'))
|
|
87
|
+
modifications = min(additions, deletions)
|
|
88
|
+
net_additions = additions - modifications
|
|
89
|
+
net_deletions = deletions - modifications
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
"filename": filename,
|
|
93
|
+
"unified_diff": unified_diff,
|
|
94
|
+
"html_diff": html_table,
|
|
95
|
+
"similarity_ratio": similarity_ratio,
|
|
96
|
+
"statistics": {
|
|
97
|
+
"additions": net_additions,
|
|
98
|
+
"deletions": net_deletions,
|
|
99
|
+
"modifications": modifications,
|
|
100
|
+
"total_changes": additions + deletions,
|
|
101
|
+
"similarity_percentage": round(similarity_ratio * 100, 2)
|
|
102
|
+
},
|
|
103
|
+
"old_line_count": len(old_lines),
|
|
104
|
+
"new_line_count": len(new_lines),
|
|
105
|
+
"line_change": len(new_lines) - len(old_lines)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
def compare_versions(self,
|
|
109
|
+
old_content_dict: Dict[str, str],
|
|
110
|
+
new_content_dict: Dict[str, str],
|
|
111
|
+
old_version: str,
|
|
112
|
+
new_version: str) -> Dict:
|
|
113
|
+
"""
|
|
114
|
+
Compare two complete versions and generate comprehensive change analysis.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
old_content_dict: Content of the old version
|
|
118
|
+
new_content_dict: Content of the new version
|
|
119
|
+
old_version: Name of the old version
|
|
120
|
+
new_version: Name of the new version
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Complete comparison report
|
|
124
|
+
"""
|
|
125
|
+
comparison = {
|
|
126
|
+
"old_version": old_version,
|
|
127
|
+
"new_version": new_version,
|
|
128
|
+
"comparison_timestamp": datetime.now().isoformat(),
|
|
129
|
+
"file_changes": {},
|
|
130
|
+
"summary": {
|
|
131
|
+
"files_added": [],
|
|
132
|
+
"files_removed": [],
|
|
133
|
+
"files_modified": [],
|
|
134
|
+
"total_additions": 0,
|
|
135
|
+
"total_deletions": 0,
|
|
136
|
+
"total_modifications": 0,
|
|
137
|
+
"average_similarity": 0
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# Find all files in both versions
|
|
142
|
+
old_files = set(old_content_dict.keys())
|
|
143
|
+
new_files = set(new_content_dict.keys())
|
|
144
|
+
|
|
145
|
+
# Categorize changes
|
|
146
|
+
added_files = new_files - old_files
|
|
147
|
+
removed_files = old_files - new_files
|
|
148
|
+
common_files = old_files & new_files
|
|
149
|
+
|
|
150
|
+
comparison["summary"]["files_added"] = list(added_files)
|
|
151
|
+
comparison["summary"]["files_removed"] = list(removed_files)
|
|
152
|
+
|
|
153
|
+
# Analyze common files
|
|
154
|
+
similarities = []
|
|
155
|
+
for filename in common_files:
|
|
156
|
+
old_content = old_content_dict[filename]
|
|
157
|
+
new_content = new_content_dict[filename]
|
|
158
|
+
|
|
159
|
+
diff_result = self.generate_diff(old_content, new_content, filename)
|
|
160
|
+
comparison["file_changes"][filename] = diff_result
|
|
161
|
+
|
|
162
|
+
# Check if file was actually modified
|
|
163
|
+
if diff_result["statistics"]["total_changes"] > 0:
|
|
164
|
+
comparison["summary"]["files_modified"].append(filename)
|
|
165
|
+
|
|
166
|
+
# Accumulate statistics
|
|
167
|
+
stats = diff_result["statistics"]
|
|
168
|
+
comparison["summary"]["total_additions"] += stats["additions"]
|
|
169
|
+
comparison["summary"]["total_deletions"] += stats["deletions"]
|
|
170
|
+
comparison["summary"]["total_modifications"] += stats["modifications"]
|
|
171
|
+
similarities.append(diff_result["similarity_ratio"])
|
|
172
|
+
|
|
173
|
+
# Handle added files
|
|
174
|
+
for filename in added_files:
|
|
175
|
+
new_content = new_content_dict[filename]
|
|
176
|
+
line_count = len(new_content.splitlines())
|
|
177
|
+
comparison["file_changes"][filename] = {
|
|
178
|
+
"filename": filename,
|
|
179
|
+
"status": "added",
|
|
180
|
+
"new_line_count": line_count,
|
|
181
|
+
"statistics": {
|
|
182
|
+
"additions": line_count,
|
|
183
|
+
"deletions": 0,
|
|
184
|
+
"modifications": 0,
|
|
185
|
+
"total_changes": line_count
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
comparison["summary"]["total_additions"] += line_count
|
|
189
|
+
|
|
190
|
+
# Handle removed files
|
|
191
|
+
for filename in removed_files:
|
|
192
|
+
old_content = old_content_dict[filename]
|
|
193
|
+
line_count = len(old_content.splitlines())
|
|
194
|
+
comparison["file_changes"][filename] = {
|
|
195
|
+
"filename": filename,
|
|
196
|
+
"status": "removed",
|
|
197
|
+
"old_line_count": line_count,
|
|
198
|
+
"statistics": {
|
|
199
|
+
"additions": 0,
|
|
200
|
+
"deletions": line_count,
|
|
201
|
+
"modifications": 0,
|
|
202
|
+
"total_changes": line_count
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
comparison["summary"]["total_deletions"] += line_count
|
|
206
|
+
|
|
207
|
+
# Calculate average similarity
|
|
208
|
+
if similarities:
|
|
209
|
+
comparison["summary"]["average_similarity"] = round(sum(similarities) / len(similarities) * 100, 2)
|
|
210
|
+
|
|
211
|
+
return comparison
|
|
212
|
+
|
|
213
|
+
def save_comparison(self,
|
|
214
|
+
comparison: Dict,
|
|
215
|
+
save_diffs: bool = True) -> Tuple[str, str]:
|
|
216
|
+
"""
|
|
217
|
+
Save a comparison report to disk.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
comparison: Comparison result from compare_versions
|
|
221
|
+
save_diffs: Whether to save individual diff files
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Tuple of (comparison_file_path, summary_file_path)
|
|
225
|
+
"""
|
|
226
|
+
old_version = comparison["old_version"]
|
|
227
|
+
new_version = comparison["new_version"]
|
|
228
|
+
|
|
229
|
+
# Save main comparison file
|
|
230
|
+
comparison_filename = f"{old_version}_to_{new_version}.json"
|
|
231
|
+
comparison_path = self.changes_dir / comparison_filename
|
|
232
|
+
|
|
233
|
+
with open(comparison_path, 'w', encoding='utf-8') as f:
|
|
234
|
+
json.dump(comparison, f, indent=2, ensure_ascii=False)
|
|
235
|
+
|
|
236
|
+
# Save individual diff files if requested
|
|
237
|
+
if save_diffs:
|
|
238
|
+
for filename, change_info in comparison["file_changes"].items():
|
|
239
|
+
if "unified_diff" in change_info:
|
|
240
|
+
diff_filename = f"{filename}_{old_version}_{new_version}.diff"
|
|
241
|
+
diff_path = self.diffs_dir / diff_filename
|
|
242
|
+
|
|
243
|
+
with open(diff_path, 'w', encoding='utf-8') as f:
|
|
244
|
+
f.writelines(change_info["unified_diff"])
|
|
245
|
+
|
|
246
|
+
# Create human-readable summary
|
|
247
|
+
summary_filename = f"{old_version}_to_{new_version}_summary.md"
|
|
248
|
+
summary_path = self.changes_dir / summary_filename
|
|
249
|
+
self._create_summary_markdown(comparison, summary_path)
|
|
250
|
+
|
|
251
|
+
return str(comparison_path), str(summary_path)
|
|
252
|
+
|
|
253
|
+
def _create_summary_markdown(self, comparison: Dict, output_path: Path):
|
|
254
|
+
"""Create a human-readable markdown summary of changes."""
|
|
255
|
+
old_version = comparison["old_version"]
|
|
256
|
+
new_version = comparison["new_version"]
|
|
257
|
+
summary = comparison["summary"]
|
|
258
|
+
|
|
259
|
+
content = f"""# Change Summary: {old_version} → {new_version}
|
|
260
|
+
|
|
261
|
+
**Generated:** {comparison["comparison_timestamp"]}
|
|
262
|
+
|
|
263
|
+
## Overview
|
|
264
|
+
|
|
265
|
+
| Metric | Count |
|
|
266
|
+
|--------|-------|
|
|
267
|
+
| Files Added | {len(summary["files_added"])} |
|
|
268
|
+
| Files Removed | {len(summary["files_removed"])} |
|
|
269
|
+
| Files Modified | {len(summary["files_modified"])} |
|
|
270
|
+
| Total Additions | +{summary["total_additions"]} lines |
|
|
271
|
+
| Total Deletions | -{summary["total_deletions"]} lines |
|
|
272
|
+
| Net Change | {summary["total_additions"] - summary["total_deletions"]:+d} lines |
|
|
273
|
+
| Average Similarity | {summary["average_similarity"]}% |
|
|
274
|
+
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
# Files added
|
|
278
|
+
if summary["files_added"]:
|
|
279
|
+
content += "## Files Added\n\n"
|
|
280
|
+
for filename in summary["files_added"]:
|
|
281
|
+
file_info = comparison["file_changes"][filename]
|
|
282
|
+
content += f"- **{filename}** (+{file_info['statistics']['additions']} lines)\n"
|
|
283
|
+
content += "\n"
|
|
284
|
+
|
|
285
|
+
# Files removed
|
|
286
|
+
if summary["files_removed"]:
|
|
287
|
+
content += "## Files Removed\n\n"
|
|
288
|
+
for filename in summary["files_removed"]:
|
|
289
|
+
file_info = comparison["file_changes"][filename]
|
|
290
|
+
content += f"- **{filename}** (-{file_info['statistics']['deletions']} lines)\n"
|
|
291
|
+
content += "\n"
|
|
292
|
+
|
|
293
|
+
# Files modified
|
|
294
|
+
if summary["files_modified"]:
|
|
295
|
+
content += "## Files Modified\n\n"
|
|
296
|
+
for filename in summary["files_modified"]:
|
|
297
|
+
file_info = comparison["file_changes"][filename]
|
|
298
|
+
stats = file_info["statistics"]
|
|
299
|
+
similarity = stats["similarity_percentage"]
|
|
300
|
+
|
|
301
|
+
content += f"### {filename}\n"
|
|
302
|
+
content += f"- **Similarity:** {similarity}%\n"
|
|
303
|
+
content += f"- **Changes:** +{stats['additions']} -{stats['deletions']} lines\n"
|
|
304
|
+
content += f"- **Modifications:** {stats['modifications']} lines\n"
|
|
305
|
+
|
|
306
|
+
# Add sample changes if available
|
|
307
|
+
if "unified_diff" in file_info and file_info["unified_diff"]:
|
|
308
|
+
content += f"- **Sample changes:**\n"
|
|
309
|
+
content += " ```diff\n"
|
|
310
|
+
# Show first few diff lines
|
|
311
|
+
diff_lines = file_info["unified_diff"][:10]
|
|
312
|
+
for line in diff_lines:
|
|
313
|
+
if line.startswith(('+', '-', '@')):
|
|
314
|
+
content += f" {line.rstrip()}\n"
|
|
315
|
+
if len(file_info["unified_diff"]) > 10:
|
|
316
|
+
content += " ...\n"
|
|
317
|
+
content += " ```\n"
|
|
318
|
+
content += "\n"
|
|
319
|
+
|
|
320
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
|
321
|
+
f.write(content)
|
|
322
|
+
|
|
323
|
+
def analyze_quality_progression(self, version_qualities: List[Tuple[str, int]]) -> Dict:
|
|
324
|
+
"""
|
|
325
|
+
Analyze quality progression across multiple versions.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
version_qualities: List of (version_name, quality_score) tuples
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
Quality progression analysis
|
|
332
|
+
"""
|
|
333
|
+
if len(version_qualities) < 2:
|
|
334
|
+
return {"error": "Need at least 2 versions for progression analysis"}
|
|
335
|
+
|
|
336
|
+
analysis = {
|
|
337
|
+
"versions_analyzed": len(version_qualities),
|
|
338
|
+
"quality_progression": [],
|
|
339
|
+
"overall_improvement": 0,
|
|
340
|
+
"best_version": None,
|
|
341
|
+
"worst_version": None,
|
|
342
|
+
"improvement_trend": "unknown"
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
# Calculate progression
|
|
346
|
+
for i in range(1, len(version_qualities)):
|
|
347
|
+
prev_version, prev_score = version_qualities[i-1]
|
|
348
|
+
curr_version, curr_score = version_qualities[i]
|
|
349
|
+
|
|
350
|
+
improvement = curr_score - prev_score
|
|
351
|
+
analysis["quality_progression"].append({
|
|
352
|
+
"from": prev_version,
|
|
353
|
+
"to": curr_version,
|
|
354
|
+
"improvement": improvement,
|
|
355
|
+
"from_score": prev_score,
|
|
356
|
+
"to_score": curr_score
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
# Overall metrics
|
|
360
|
+
first_score = version_qualities[0][1]
|
|
361
|
+
last_score = version_qualities[-1][1]
|
|
362
|
+
analysis["overall_improvement"] = last_score - first_score
|
|
363
|
+
|
|
364
|
+
# Best and worst versions
|
|
365
|
+
best_score = max(version_qualities, key=lambda x: x[1])
|
|
366
|
+
worst_score = min(version_qualities, key=lambda x: x[1])
|
|
367
|
+
analysis["best_version"] = {"name": best_score[0], "score": best_score[1]}
|
|
368
|
+
analysis["worst_version"] = {"name": worst_score[0], "score": worst_score[1]}
|
|
369
|
+
|
|
370
|
+
# Determine trend
|
|
371
|
+
improvements = [step["improvement"] for step in analysis["quality_progression"]]
|
|
372
|
+
positive_steps = sum(1 for imp in improvements if imp > 0)
|
|
373
|
+
negative_steps = sum(1 for imp in improvements if imp < 0)
|
|
374
|
+
|
|
375
|
+
if positive_steps > negative_steps:
|
|
376
|
+
analysis["improvement_trend"] = "improving"
|
|
377
|
+
elif negative_steps > positive_steps:
|
|
378
|
+
analysis["improvement_trend"] = "declining"
|
|
379
|
+
else:
|
|
380
|
+
analysis["improvement_trend"] = "stable"
|
|
381
|
+
|
|
382
|
+
return analysis
|
|
383
|
+
|
|
384
|
+
def create_change_report(self,
|
|
385
|
+
old_version: str,
|
|
386
|
+
new_version: str,
|
|
387
|
+
old_content: Dict[str, str],
|
|
388
|
+
new_content: Dict[str, str],
|
|
389
|
+
old_quality: Optional[int] = None,
|
|
390
|
+
new_quality: Optional[int] = None) -> str:
|
|
391
|
+
"""
|
|
392
|
+
Create a comprehensive change report.
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
old_version: Name of the old version
|
|
396
|
+
new_version: Name of the new version
|
|
397
|
+
old_content: Content of the old version
|
|
398
|
+
new_content: Content of the new version
|
|
399
|
+
old_quality: Quality score of old version
|
|
400
|
+
new_quality: Quality score of new version
|
|
401
|
+
|
|
402
|
+
Returns:
|
|
403
|
+
Path to the generated report
|
|
404
|
+
"""
|
|
405
|
+
# Generate comparison
|
|
406
|
+
comparison = self.compare_versions(old_content, new_content, old_version, new_version)
|
|
407
|
+
|
|
408
|
+
# Add quality information if provided
|
|
409
|
+
if old_quality is not None and new_quality is not None:
|
|
410
|
+
comparison["quality_analysis"] = {
|
|
411
|
+
"old_quality_score": old_quality,
|
|
412
|
+
"new_quality_score": new_quality,
|
|
413
|
+
"quality_improvement": new_quality - old_quality
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
# Save comparison
|
|
417
|
+
comparison_path, summary_path = self.save_comparison(comparison)
|
|
418
|
+
|
|
419
|
+
return summary_path
|