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
tools/version_manager.py
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Version Manager - Milestone 2
|
|
3
|
+
|
|
4
|
+
Handles content versioning, tracking, and management for iterative improvements.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import shutil
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from typing import Dict, List, Optional, Tuple
|
|
12
|
+
import hashlib
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class VersionManager:
|
|
16
|
+
"""
|
|
17
|
+
Manages content versions and provides version tracking capabilities.
|
|
18
|
+
|
|
19
|
+
Features:
|
|
20
|
+
- Create and track content versions
|
|
21
|
+
- Generate version manifests
|
|
22
|
+
- Handle rollback operations
|
|
23
|
+
- Maintain version lineage
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, base_dir: str = "artifacts"):
|
|
27
|
+
"""
|
|
28
|
+
Initialize the version manager.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
base_dir: Base directory for all artifacts
|
|
32
|
+
"""
|
|
33
|
+
self.base_dir = Path(base_dir)
|
|
34
|
+
self.content_dir = self.base_dir / "reviewed_content"
|
|
35
|
+
self.history_dir = self.base_dir / "version_history"
|
|
36
|
+
self.manifest_path = self.history_dir / "version_manifest.json"
|
|
37
|
+
|
|
38
|
+
# Ensure directories exist
|
|
39
|
+
self.content_dir.mkdir(parents=True, exist_ok=True)
|
|
40
|
+
self.history_dir.mkdir(parents=True, exist_ok=True)
|
|
41
|
+
(self.history_dir / "changes").mkdir(exist_ok=True)
|
|
42
|
+
(self.history_dir / "diffs").mkdir(exist_ok=True)
|
|
43
|
+
|
|
44
|
+
# Initialize manifest if it doesn't exist
|
|
45
|
+
self._init_manifest()
|
|
46
|
+
|
|
47
|
+
def _init_manifest(self):
|
|
48
|
+
"""Initialize the version manifest file."""
|
|
49
|
+
if not self.manifest_path.exists():
|
|
50
|
+
manifest = {
|
|
51
|
+
"versions": {},
|
|
52
|
+
"latest_version": None,
|
|
53
|
+
"created_at": datetime.now().isoformat(),
|
|
54
|
+
"last_updated": datetime.now().isoformat()
|
|
55
|
+
}
|
|
56
|
+
self._save_manifest(manifest)
|
|
57
|
+
|
|
58
|
+
def _load_manifest(self) -> Dict:
|
|
59
|
+
"""Load the version manifest."""
|
|
60
|
+
with open(self.manifest_path, 'r', encoding='utf-8') as f:
|
|
61
|
+
return json.load(f)
|
|
62
|
+
|
|
63
|
+
def _save_manifest(self, manifest: Dict):
|
|
64
|
+
"""Save the version manifest."""
|
|
65
|
+
manifest["last_updated"] = datetime.now().isoformat()
|
|
66
|
+
with open(self.manifest_path, 'w', encoding='utf-8') as f:
|
|
67
|
+
json.dump(manifest, f, indent=2, ensure_ascii=False)
|
|
68
|
+
|
|
69
|
+
def _calculate_content_hash(self, content_dict: Dict[str, str]) -> str:
|
|
70
|
+
"""Calculate a hash of the content for integrity checking."""
|
|
71
|
+
content_str = json.dumps(content_dict, sort_keys=True)
|
|
72
|
+
return hashlib.sha256(content_str.encode()).hexdigest()[:16]
|
|
73
|
+
|
|
74
|
+
def create_version(self,
|
|
75
|
+
content_dict: Dict[str, str],
|
|
76
|
+
version_name: str,
|
|
77
|
+
agent_name: str = "unknown",
|
|
78
|
+
parent_version: Optional[str] = None,
|
|
79
|
+
metadata: Optional[Dict] = None) -> Dict:
|
|
80
|
+
"""
|
|
81
|
+
Create a new content version.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
content_dict: Dictionary of filename -> content
|
|
85
|
+
version_name: Name for this version (e.g., "v1_content_edited")
|
|
86
|
+
agent_name: Name of the agent that created this version
|
|
87
|
+
parent_version: Name of the parent version (if any)
|
|
88
|
+
metadata: Additional metadata for this version
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Version info dictionary
|
|
92
|
+
"""
|
|
93
|
+
manifest = self._load_manifest()
|
|
94
|
+
|
|
95
|
+
# Check if version already exists
|
|
96
|
+
if version_name in manifest["versions"]:
|
|
97
|
+
raise ValueError(f"Version {version_name} already exists")
|
|
98
|
+
|
|
99
|
+
# Create version directory
|
|
100
|
+
version_dir = self.content_dir / version_name
|
|
101
|
+
version_dir.mkdir(exist_ok=True)
|
|
102
|
+
|
|
103
|
+
# Save content files
|
|
104
|
+
for filename, content in content_dict.items():
|
|
105
|
+
file_path = version_dir / filename
|
|
106
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
107
|
+
f.write(content)
|
|
108
|
+
|
|
109
|
+
# Create version metadata
|
|
110
|
+
version_info = {
|
|
111
|
+
"name": version_name,
|
|
112
|
+
"agent": agent_name,
|
|
113
|
+
"parent_version": parent_version,
|
|
114
|
+
"created_at": datetime.now().isoformat(),
|
|
115
|
+
"content_hash": self._calculate_content_hash(content_dict),
|
|
116
|
+
"files": list(content_dict.keys()),
|
|
117
|
+
"file_count": len(content_dict),
|
|
118
|
+
"directory": str(version_dir.relative_to(self.base_dir)),
|
|
119
|
+
"metadata": metadata or {}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# Update manifest
|
|
123
|
+
manifest["versions"][version_name] = version_info
|
|
124
|
+
manifest["latest_version"] = version_name
|
|
125
|
+
self._save_manifest(manifest)
|
|
126
|
+
|
|
127
|
+
# Update current symlink
|
|
128
|
+
self._update_current_symlink(version_name)
|
|
129
|
+
|
|
130
|
+
return version_info
|
|
131
|
+
|
|
132
|
+
def get_version(self, version_name: str) -> Optional[Dict]:
|
|
133
|
+
"""
|
|
134
|
+
Get information about a specific version.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
version_name: Name of the version to retrieve
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Version info dictionary or None if not found
|
|
141
|
+
"""
|
|
142
|
+
manifest = self._load_manifest()
|
|
143
|
+
return manifest["versions"].get(version_name)
|
|
144
|
+
|
|
145
|
+
def get_version_content(self, version_name: str) -> Dict[str, str]:
|
|
146
|
+
"""
|
|
147
|
+
Load the content of a specific version.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
version_name: Name of the version to load
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Dictionary of filename -> content
|
|
154
|
+
"""
|
|
155
|
+
version_info = self.get_version(version_name)
|
|
156
|
+
if not version_info:
|
|
157
|
+
raise ValueError(f"Version {version_name} not found")
|
|
158
|
+
|
|
159
|
+
version_dir = self.base_dir / version_info["directory"]
|
|
160
|
+
content_dict = {}
|
|
161
|
+
|
|
162
|
+
for filename in version_info["files"]:
|
|
163
|
+
file_path = version_dir / filename
|
|
164
|
+
if file_path.exists():
|
|
165
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
166
|
+
content_dict[filename] = f.read()
|
|
167
|
+
|
|
168
|
+
return content_dict
|
|
169
|
+
|
|
170
|
+
def list_versions(self) -> List[Dict]:
|
|
171
|
+
"""
|
|
172
|
+
List all versions in chronological order.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
List of version info dictionaries
|
|
176
|
+
"""
|
|
177
|
+
manifest = self._load_manifest()
|
|
178
|
+
versions = list(manifest["versions"].values())
|
|
179
|
+
|
|
180
|
+
# Sort by creation date
|
|
181
|
+
versions.sort(key=lambda v: v["created_at"])
|
|
182
|
+
|
|
183
|
+
return versions
|
|
184
|
+
|
|
185
|
+
def get_latest_version(self) -> Optional[str]:
|
|
186
|
+
"""
|
|
187
|
+
Get the name of the latest version.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
Latest version name or None if no versions exist
|
|
191
|
+
"""
|
|
192
|
+
manifest = self._load_manifest()
|
|
193
|
+
return manifest.get("latest_version")
|
|
194
|
+
|
|
195
|
+
def get_version_lineage(self, version_name: str) -> List[str]:
|
|
196
|
+
"""
|
|
197
|
+
Get the lineage (ancestry) of a version.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
version_name: Version to trace lineage for
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
List of version names from root to specified version
|
|
204
|
+
"""
|
|
205
|
+
lineage = []
|
|
206
|
+
current = version_name
|
|
207
|
+
|
|
208
|
+
while current:
|
|
209
|
+
version_info = self.get_version(current)
|
|
210
|
+
if not version_info:
|
|
211
|
+
break
|
|
212
|
+
|
|
213
|
+
lineage.insert(0, current)
|
|
214
|
+
current = version_info.get("parent_version")
|
|
215
|
+
|
|
216
|
+
return lineage
|
|
217
|
+
|
|
218
|
+
def rollback_to_version(self, version_name: str) -> Dict:
|
|
219
|
+
"""
|
|
220
|
+
Rollback to a specific version (updates current symlink).
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
version_name: Version to rollback to
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
Version info of the rollback target
|
|
227
|
+
"""
|
|
228
|
+
version_info = self.get_version(version_name)
|
|
229
|
+
if not version_info:
|
|
230
|
+
raise ValueError(f"Version {version_name} not found")
|
|
231
|
+
|
|
232
|
+
# Update current symlink
|
|
233
|
+
self._update_current_symlink(version_name)
|
|
234
|
+
|
|
235
|
+
# Update manifest to mark this as latest
|
|
236
|
+
manifest = self._load_manifest()
|
|
237
|
+
manifest["latest_version"] = version_name
|
|
238
|
+
self._save_manifest(manifest)
|
|
239
|
+
|
|
240
|
+
return version_info
|
|
241
|
+
|
|
242
|
+
def delete_version(self, version_name: str) -> bool:
|
|
243
|
+
"""
|
|
244
|
+
Delete a version (use with caution).
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
version_name: Version to delete
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
True if deletion was successful
|
|
251
|
+
"""
|
|
252
|
+
version_info = self.get_version(version_name)
|
|
253
|
+
if not version_info:
|
|
254
|
+
return False
|
|
255
|
+
|
|
256
|
+
# Remove directory
|
|
257
|
+
version_dir = self.base_dir / version_info["directory"]
|
|
258
|
+
if version_dir.exists():
|
|
259
|
+
shutil.rmtree(version_dir)
|
|
260
|
+
|
|
261
|
+
# Update manifest
|
|
262
|
+
manifest = self._load_manifest()
|
|
263
|
+
del manifest["versions"][version_name]
|
|
264
|
+
|
|
265
|
+
# Update latest if this was the latest
|
|
266
|
+
if manifest.get("latest_version") == version_name:
|
|
267
|
+
versions = list(manifest["versions"].values())
|
|
268
|
+
if versions:
|
|
269
|
+
latest = max(versions, key=lambda v: v["created_at"])
|
|
270
|
+
manifest["latest_version"] = latest["name"]
|
|
271
|
+
else:
|
|
272
|
+
manifest["latest_version"] = None
|
|
273
|
+
|
|
274
|
+
self._save_manifest(manifest)
|
|
275
|
+
return True
|
|
276
|
+
|
|
277
|
+
def _update_current_symlink(self, version_name: str):
|
|
278
|
+
"""Update the 'current' pointer to the specified version."""
|
|
279
|
+
current_link = self.content_dir / "current"
|
|
280
|
+
target_dir = version_name
|
|
281
|
+
|
|
282
|
+
# Try symlink first, fall back to text file on Windows if permission denied
|
|
283
|
+
try:
|
|
284
|
+
# Remove existing symlink
|
|
285
|
+
if current_link.exists() or current_link.is_symlink():
|
|
286
|
+
current_link.unlink()
|
|
287
|
+
# Create new symlink
|
|
288
|
+
current_link.symlink_to(target_dir)
|
|
289
|
+
except OSError:
|
|
290
|
+
# Windows without admin rights - use a text file instead
|
|
291
|
+
current_file = self.content_dir / "current.txt"
|
|
292
|
+
with open(current_file, 'w', encoding='utf-8') as f:
|
|
293
|
+
f.write(version_name)
|
|
294
|
+
|
|
295
|
+
def get_version_stats(self) -> Dict:
|
|
296
|
+
"""
|
|
297
|
+
Get statistics about all versions.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
Statistics dictionary
|
|
301
|
+
"""
|
|
302
|
+
versions = self.list_versions()
|
|
303
|
+
|
|
304
|
+
if not versions:
|
|
305
|
+
return {
|
|
306
|
+
"total_versions": 0,
|
|
307
|
+
"earliest_version": None,
|
|
308
|
+
"latest_version": None,
|
|
309
|
+
"agents_used": [],
|
|
310
|
+
"total_files": 0
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
agents = set()
|
|
314
|
+
total_files = 0
|
|
315
|
+
|
|
316
|
+
for version in versions:
|
|
317
|
+
agents.add(version.get("agent", "unknown"))
|
|
318
|
+
total_files += version.get("file_count", 0)
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
"total_versions": len(versions),
|
|
322
|
+
"earliest_version": versions[0]["name"],
|
|
323
|
+
"latest_version": versions[-1]["name"],
|
|
324
|
+
"agents_used": list(agents),
|
|
325
|
+
"total_files": total_files,
|
|
326
|
+
"average_files_per_version": total_files / len(versions) if versions else 0
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
def export_version_history(self, output_path: str):
|
|
330
|
+
"""Export complete version history to a JSON file."""
|
|
331
|
+
manifest = self._load_manifest()
|
|
332
|
+
stats = self.get_version_stats()
|
|
333
|
+
|
|
334
|
+
export_data = {
|
|
335
|
+
"export_timestamp": datetime.now().isoformat(),
|
|
336
|
+
"statistics": stats,
|
|
337
|
+
"manifest": manifest,
|
|
338
|
+
"version_lineages": {}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
# Add lineage for each version
|
|
342
|
+
for version_name in manifest["versions"]:
|
|
343
|
+
export_data["version_lineages"][version_name] = self.get_version_lineage(version_name)
|
|
344
|
+
|
|
345
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
|
346
|
+
json.dump(export_data, f, indent=2, ensure_ascii=False)
|