gitview 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.
- gitview/__init__.py +3 -0
- gitview/backends/__init__.py +18 -0
- gitview/backends/anthropic_backend.py +66 -0
- gitview/backends/base.py +60 -0
- gitview/backends/ollama_backend.py +96 -0
- gitview/backends/openai_backend.py +71 -0
- gitview/backends/router.py +164 -0
- gitview/chunker.py +367 -0
- gitview/cli.py +332 -0
- gitview/extractor.py +423 -0
- gitview/storyteller.py +352 -0
- gitview/summarizer.py +270 -0
- gitview/writer.py +271 -0
- gitview-0.1.0.dist-info/METADATA +468 -0
- gitview-0.1.0.dist-info/RECORD +19 -0
- gitview-0.1.0.dist-info/WHEEL +5 -0
- gitview-0.1.0.dist-info/entry_points.txt +2 -0
- gitview-0.1.0.dist-info/licenses/LICENSE +21 -0
- gitview-0.1.0.dist-info/top_level.txt +1 -0
gitview/writer.py
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""Output writers for git history stories."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict, List, Any
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
from .chunker import Phase
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class OutputWriter:
|
|
12
|
+
"""Write git history stories to various formats."""
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def write_markdown(stories: Dict[str, str], phases: List[Phase],
|
|
16
|
+
output_path: str, repo_name: str = "Repository"):
|
|
17
|
+
"""
|
|
18
|
+
Write comprehensive markdown report.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
stories: Dict of story sections from storyteller
|
|
22
|
+
phases: List of Phase objects
|
|
23
|
+
output_path: Path to output markdown file
|
|
24
|
+
repo_name: Repository name for title
|
|
25
|
+
"""
|
|
26
|
+
output_file = Path(output_path)
|
|
27
|
+
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
28
|
+
|
|
29
|
+
with open(output_file, 'w') as f:
|
|
30
|
+
# Header
|
|
31
|
+
f.write(f"# Evolution of {repo_name}\n\n")
|
|
32
|
+
f.write(f"*Generated on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*\n\n")
|
|
33
|
+
f.write("---\n\n")
|
|
34
|
+
|
|
35
|
+
# Table of Contents
|
|
36
|
+
f.write("## Table of Contents\n\n")
|
|
37
|
+
f.write("1. [Executive Summary](#executive-summary)\n")
|
|
38
|
+
f.write("2. [Timeline](#timeline)\n")
|
|
39
|
+
f.write("3. [Full Narrative](#full-narrative)\n")
|
|
40
|
+
f.write("4. [Technical Evolution](#technical-evolution)\n")
|
|
41
|
+
f.write("5. [Story of Deletions](#story-of-deletions)\n")
|
|
42
|
+
f.write("6. [Phase Details](#phase-details)\n")
|
|
43
|
+
f.write("7. [Statistics](#statistics)\n\n")
|
|
44
|
+
f.write("---\n\n")
|
|
45
|
+
|
|
46
|
+
# Executive Summary
|
|
47
|
+
f.write("## Executive Summary\n\n")
|
|
48
|
+
f.write(stories['executive_summary'])
|
|
49
|
+
f.write("\n\n---\n\n")
|
|
50
|
+
|
|
51
|
+
# Timeline
|
|
52
|
+
f.write("## Timeline\n\n")
|
|
53
|
+
f.write(stories['timeline'])
|
|
54
|
+
f.write("\n\n---\n\n")
|
|
55
|
+
|
|
56
|
+
# Full Narrative
|
|
57
|
+
f.write("## Full Narrative\n\n")
|
|
58
|
+
f.write(stories['full_narrative'])
|
|
59
|
+
f.write("\n\n---\n\n")
|
|
60
|
+
|
|
61
|
+
# Technical Evolution
|
|
62
|
+
f.write("## Technical Evolution\n\n")
|
|
63
|
+
f.write(stories['technical_evolution'])
|
|
64
|
+
f.write("\n\n---\n\n")
|
|
65
|
+
|
|
66
|
+
# Deletion Story
|
|
67
|
+
f.write("## Story of Deletions\n\n")
|
|
68
|
+
f.write(stories['deletion_story'])
|
|
69
|
+
f.write("\n\n---\n\n")
|
|
70
|
+
|
|
71
|
+
# Phase Details
|
|
72
|
+
f.write("## Phase Details\n\n")
|
|
73
|
+
|
|
74
|
+
for phase in phases:
|
|
75
|
+
f.write(f"### Phase {phase.phase_number}\n\n")
|
|
76
|
+
f.write(f"**Period:** {phase.start_date[:10]} to {phase.end_date[:10]}\n\n")
|
|
77
|
+
|
|
78
|
+
# Stats table
|
|
79
|
+
f.write("| Metric | Value |\n")
|
|
80
|
+
f.write("|--------|-------|\n")
|
|
81
|
+
f.write(f"| Commits | {phase.commit_count} |\n")
|
|
82
|
+
f.write(f"| LOC Start | {phase.loc_start:,} |\n")
|
|
83
|
+
f.write(f"| LOC End | {phase.loc_end:,} |\n")
|
|
84
|
+
f.write(f"| LOC Delta | {phase.loc_delta:+,d} ({phase.loc_delta_percent:+.1f}%) |\n")
|
|
85
|
+
f.write(f"| Insertions | +{phase.total_insertions:,} |\n")
|
|
86
|
+
f.write(f"| Deletions | -{phase.total_deletions:,} |\n")
|
|
87
|
+
f.write(f"| Authors | {', '.join(phase.authors)} |\n")
|
|
88
|
+
f.write(f"| Primary Author | {phase.primary_author} |\n\n")
|
|
89
|
+
|
|
90
|
+
# Events
|
|
91
|
+
events = []
|
|
92
|
+
if phase.has_large_deletion:
|
|
93
|
+
events.append("🗑️ Large Deletion")
|
|
94
|
+
if phase.has_large_addition:
|
|
95
|
+
events.append("➕ Large Addition")
|
|
96
|
+
if phase.has_refactor:
|
|
97
|
+
events.append("♻️ Refactoring")
|
|
98
|
+
if phase.readme_changed:
|
|
99
|
+
events.append("📝 README Changed")
|
|
100
|
+
|
|
101
|
+
if events:
|
|
102
|
+
f.write(f"**Events:** {' | '.join(events)}\n\n")
|
|
103
|
+
|
|
104
|
+
# Summary
|
|
105
|
+
if phase.summary:
|
|
106
|
+
f.write("**Summary:**\n\n")
|
|
107
|
+
f.write(phase.summary)
|
|
108
|
+
f.write("\n\n")
|
|
109
|
+
|
|
110
|
+
f.write("---\n\n")
|
|
111
|
+
|
|
112
|
+
# Statistics
|
|
113
|
+
f.write("## Statistics\n\n")
|
|
114
|
+
OutputWriter._write_statistics(f, phases)
|
|
115
|
+
|
|
116
|
+
@staticmethod
|
|
117
|
+
def _write_statistics(f, phases: List[Phase]):
|
|
118
|
+
"""Write statistics section to markdown file."""
|
|
119
|
+
total_commits = sum(p.commit_count for p in phases)
|
|
120
|
+
total_insertions = sum(p.total_insertions for p in phases)
|
|
121
|
+
total_deletions = sum(p.total_deletions for p in phases)
|
|
122
|
+
|
|
123
|
+
all_authors = set()
|
|
124
|
+
for p in phases:
|
|
125
|
+
all_authors.update(p.authors)
|
|
126
|
+
|
|
127
|
+
# Overall stats
|
|
128
|
+
f.write("### Overall Statistics\n\n")
|
|
129
|
+
f.write("| Metric | Value |\n")
|
|
130
|
+
f.write("|--------|-------|\n")
|
|
131
|
+
f.write(f"| Total Phases | {len(phases)} |\n")
|
|
132
|
+
f.write(f"| Total Commits | {total_commits:,} |\n")
|
|
133
|
+
f.write(f"| Total Insertions | +{total_insertions:,} |\n")
|
|
134
|
+
f.write(f"| Total Deletions | -{total_deletions:,} |\n")
|
|
135
|
+
f.write(f"| Net Change | {total_insertions - total_deletions:+,d} |\n")
|
|
136
|
+
f.write(f"| Contributors | {len(all_authors)} |\n")
|
|
137
|
+
f.write(f"| Time Span | {phases[0].start_date[:10]} to {phases[-1].end_date[:10]} |\n\n")
|
|
138
|
+
|
|
139
|
+
# Phase-by-phase stats
|
|
140
|
+
f.write("### Phase-by-Phase Statistics\n\n")
|
|
141
|
+
f.write("| Phase | Period | Commits | LOC Δ | Δ% | Insertions | Deletions |\n")
|
|
142
|
+
f.write("|-------|--------|---------|-------|-----|------------|------------|\n")
|
|
143
|
+
|
|
144
|
+
for p in phases:
|
|
145
|
+
f.write(f"| {p.phase_number} | {p.start_date[:10]} to {p.end_date[:10]} | "
|
|
146
|
+
f"{p.commit_count} | {p.loc_delta:+,d} | {p.loc_delta_percent:+.1f}% | "
|
|
147
|
+
f"+{p.total_insertions:,} | -{p.total_deletions:,} |\n")
|
|
148
|
+
|
|
149
|
+
f.write("\n")
|
|
150
|
+
|
|
151
|
+
# Language evolution
|
|
152
|
+
f.write("### Language Evolution\n\n")
|
|
153
|
+
|
|
154
|
+
# Collect all languages
|
|
155
|
+
all_languages = set()
|
|
156
|
+
for p in phases:
|
|
157
|
+
all_languages.update(p.languages_start.keys())
|
|
158
|
+
all_languages.update(p.languages_end.keys())
|
|
159
|
+
|
|
160
|
+
if all_languages:
|
|
161
|
+
f.write("Languages detected across phases:\n\n")
|
|
162
|
+
for lang in sorted(all_languages):
|
|
163
|
+
f.write(f"- {lang}\n")
|
|
164
|
+
f.write("\n")
|
|
165
|
+
|
|
166
|
+
@staticmethod
|
|
167
|
+
def write_json(stories: Dict[str, str], phases: List[Phase], output_path: str):
|
|
168
|
+
"""
|
|
169
|
+
Write complete data to JSON file.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
stories: Dict of story sections
|
|
173
|
+
phases: List of Phase objects
|
|
174
|
+
output_path: Path to output JSON file
|
|
175
|
+
"""
|
|
176
|
+
output_file = Path(output_path)
|
|
177
|
+
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
178
|
+
|
|
179
|
+
data = {
|
|
180
|
+
'generated_at': datetime.now().isoformat(),
|
|
181
|
+
'total_phases': len(phases),
|
|
182
|
+
'total_commits': sum(p.commit_count for p in phases),
|
|
183
|
+
'stories': stories,
|
|
184
|
+
'phases': [p.to_dict() for p in phases],
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
with open(output_file, 'w') as f:
|
|
188
|
+
json.dump(data, f, indent=2)
|
|
189
|
+
|
|
190
|
+
@staticmethod
|
|
191
|
+
def write_simple_timeline(phases: List[Phase], output_path: str):
|
|
192
|
+
"""
|
|
193
|
+
Write a simple timeline markdown (without LLM-generated content).
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
phases: List of Phase objects
|
|
197
|
+
output_path: Path to output markdown file
|
|
198
|
+
"""
|
|
199
|
+
output_file = Path(output_path)
|
|
200
|
+
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
201
|
+
|
|
202
|
+
with open(output_file, 'w') as f:
|
|
203
|
+
f.write("# Repository Timeline\n\n")
|
|
204
|
+
f.write(f"*Generated on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*\n\n")
|
|
205
|
+
|
|
206
|
+
for phase in phases:
|
|
207
|
+
f.write(f"## Phase {phase.phase_number}: "
|
|
208
|
+
f"{phase.start_date[:10]} to {phase.end_date[:10]}\n\n")
|
|
209
|
+
|
|
210
|
+
f.write(f"- **Commits:** {phase.commit_count}\n")
|
|
211
|
+
f.write(f"- **LOC Change:** {phase.loc_delta:+,d} "
|
|
212
|
+
f"({phase.loc_delta_percent:+.1f}%)\n")
|
|
213
|
+
f.write(f"- **Authors:** {', '.join(phase.authors)}\n")
|
|
214
|
+
|
|
215
|
+
events = []
|
|
216
|
+
if phase.has_large_deletion:
|
|
217
|
+
events.append("Large Deletion")
|
|
218
|
+
if phase.has_large_addition:
|
|
219
|
+
events.append("Large Addition")
|
|
220
|
+
if phase.has_refactor:
|
|
221
|
+
events.append("Refactoring")
|
|
222
|
+
if phase.readme_changed:
|
|
223
|
+
events.append("README Changed")
|
|
224
|
+
|
|
225
|
+
if events:
|
|
226
|
+
f.write(f"- **Events:** {', '.join(events)}\n")
|
|
227
|
+
|
|
228
|
+
f.write("\n")
|
|
229
|
+
|
|
230
|
+
# Key commits
|
|
231
|
+
significant = [c for c in phase.commits
|
|
232
|
+
if c.is_large_deletion or c.is_large_addition or c.is_refactor]
|
|
233
|
+
|
|
234
|
+
if significant:
|
|
235
|
+
f.write("**Significant Commits:**\n\n")
|
|
236
|
+
for commit in significant[:5]:
|
|
237
|
+
f.write(f"- `{commit.short_hash}` - {commit.commit_subject} "
|
|
238
|
+
f"(+{commit.insertions}/-{commit.deletions})\n")
|
|
239
|
+
f.write("\n")
|
|
240
|
+
|
|
241
|
+
f.write("---\n\n")
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def write_output(stories: Dict[str, str], phases: List[Phase],
|
|
245
|
+
output_dir: str = "docs", repo_name: str = "Repository"):
|
|
246
|
+
"""
|
|
247
|
+
Write all output formats.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
stories: Dict of story sections
|
|
251
|
+
phases: List of Phase objects
|
|
252
|
+
output_dir: Directory for output files
|
|
253
|
+
repo_name: Repository name
|
|
254
|
+
"""
|
|
255
|
+
output_path = Path(output_dir)
|
|
256
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
257
|
+
|
|
258
|
+
# Write main markdown report
|
|
259
|
+
markdown_path = output_path / "history_story.md"
|
|
260
|
+
OutputWriter.write_markdown(stories, phases, str(markdown_path), repo_name)
|
|
261
|
+
print(f"Wrote markdown report to: {markdown_path}")
|
|
262
|
+
|
|
263
|
+
# Write JSON data
|
|
264
|
+
json_path = output_path / "history_data.json"
|
|
265
|
+
OutputWriter.write_json(stories, phases, str(json_path))
|
|
266
|
+
print(f"Wrote JSON data to: {json_path}")
|
|
267
|
+
|
|
268
|
+
# Write simple timeline
|
|
269
|
+
timeline_path = output_path / "timeline.md"
|
|
270
|
+
OutputWriter.write_simple_timeline(phases, str(timeline_path))
|
|
271
|
+
print(f"Wrote timeline to: {timeline_path}")
|