gitgotchi 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.
- gitgotchi-0.1.0.dist-info/METADATA +78 -0
- gitgotchi-0.1.0.dist-info/RECORD +23 -0
- gitgotchi-0.1.0.dist-info/WHEEL +5 -0
- gitgotchi-0.1.0.dist-info/entry_points.txt +2 -0
- gitgotchi-0.1.0.dist-info/licenses/LICENSE +21 -0
- gitgotchi-0.1.0.dist-info/top_level.txt +1 -0
- src/__init__.py +13 -0
- src/cli.py +182 -0
- src/db/__init__.py +1 -0
- src/db/migrations.py +100 -0
- src/db/models.py +92 -0
- src/db/state_manager.py +277 -0
- src/hooks/__init__.py +1 -0
- src/hooks/installer.py +128 -0
- src/hooks/post_commit.py +70 -0
- src/pet/__init__.py +1 -0
- src/pet/evolution.py +5 -0
- src/pet/renderer.py +208 -0
- src/pet/states.py +116 -0
- src/seance/__init__.py +1 -0
- src/seance/git_analyzer.py +5 -0
- src/seance/story_gen.py +177 -0
- src/seance/templates.py +3 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gitgotchi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Stardew Valley-inspired terminal companion for your git repository
|
|
5
|
+
Home-page: https://github.com/yourusername/gitgotchi
|
|
6
|
+
Author: GitGotchi Team
|
|
7
|
+
Author-email: GitGotchi Team <gitgotchi@example.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Homepage, https://github.com/yourusername/gitgotchi
|
|
10
|
+
Project-URL: Repository, https://github.com/yourusername/gitgotchi
|
|
11
|
+
Project-URL: Issues, https://github.com/yourusername/gitgotchi/issues
|
|
12
|
+
Project-URL: Documentation, https://github.com/yourusername/gitgotchi#readme
|
|
13
|
+
Keywords: git,terminal,pet,companion,stardew-valley,ai,tamagotchi,devtools,hackathon,kiro
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: rich>=13.7.0
|
|
26
|
+
Requires-Dist: gitpython>=3.1.40
|
|
27
|
+
Requires-Dist: anthropic>=0.34.0
|
|
28
|
+
Requires-Dist: sqlalchemy>=2.0.36
|
|
29
|
+
Requires-Dist: typer>=0.9.0
|
|
30
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=7.4.3; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
34
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
36
|
+
Dynamic: author
|
|
37
|
+
Dynamic: home-page
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
Dynamic: requires-python
|
|
40
|
+
|
|
41
|
+
# 🎃 GitGotchi - Your Friendly Dev Companion 👻
|
|
42
|
+
|
|
43
|
+
A Stardew Valley-inspired terminal companion that lives in your git repository!
|
|
44
|
+
|
|
45
|
+
## ✨ Features
|
|
46
|
+
- Adorable pixel art companion that evolves based on your coding habits
|
|
47
|
+
- Warm, encouraging stories about your code history
|
|
48
|
+
- Automatic feeding through git hooks
|
|
49
|
+
- Friendship system inspired by Stardew Valley
|
|
50
|
+
- Cozy terminal UI with sparkles and hearts
|
|
51
|
+
- Fast evolution - see changes every 2-3 commits!
|
|
52
|
+
|
|
53
|
+
## 🚀 Quick Start
|
|
54
|
+
```bash
|
|
55
|
+
pip install -r requirements.txt
|
|
56
|
+
python gitgotchi.py install # Install git hooks
|
|
57
|
+
python gitgotchi.py status # Meet your companion!
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## 🎮 Commands
|
|
61
|
+
- `gitgotchi status` - See your companion
|
|
62
|
+
- `gitgotchi pet` - Give them affection
|
|
63
|
+
- `gitgotchi story` - Hear a code story
|
|
64
|
+
- `gitgotchi evolve` - Check evolution progress
|
|
65
|
+
- `gitgotchi name <name>` - Give your companion a name
|
|
66
|
+
|
|
67
|
+
## 🌟 Evolution Forms (Fast Track!)
|
|
68
|
+
- 🥚 Egg (0-2 commits)
|
|
69
|
+
- 👻 Ghost (3-5 commits)
|
|
70
|
+
- 👼 Angel (6+ commits, high quality)
|
|
71
|
+
- 😈 Demon (6+ commits, chaotic)
|
|
72
|
+
- 🧟 Zombie (4+ reverts, needs coffee)
|
|
73
|
+
- 🌙 Wraith (15+ commits, ultimate form)
|
|
74
|
+
|
|
75
|
+
## 🎨 Theme
|
|
76
|
+
GitGotchi is inspired by Stardew Valley's cozy, friendly aesthetic. Your companion is a warm, magical spirit - not scary! Think Junimos, sparkles, and autumn leaves.
|
|
77
|
+
|
|
78
|
+
Built with ❤️ for Kiroween Hackathon 2024
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
gitgotchi-0.1.0.dist-info/licenses/LICENSE,sha256=6k9l52nYfuS1Ktvxos5VGH5kVc8oxgykbKSzmh9tzqo,1092
|
|
2
|
+
src/__init__.py,sha256=l_V_FObzZCzbJFX4e2u13AnYZEdE29dtdpSqYwfiMTk,284
|
|
3
|
+
src/cli.py,sha256=WerXS_SX6of7SHn3u5JCpHE-XeI59CX6FX9dezV-6U4,5258
|
|
4
|
+
src/db/__init__.py,sha256=LjqUEYU9TURCkPMkKAot0T9khEHOrkGZwcShXhuFobo,24
|
|
5
|
+
src/db/migrations.py,sha256=fsKnfgU8uu7R0aHdmqq89dR2adXwRk2On_28Ypn56a4,3029
|
|
6
|
+
src/db/models.py,sha256=QdXHkxR9ZaONaiup5q6NYvv6cxTw-HUntrDipzcv_CM,3573
|
|
7
|
+
src/db/state_manager.py,sha256=Xr_L_qRC9x58ac0M4tkIZWUfV7XuIKN7zJFB0JIvLQQ,8871
|
|
8
|
+
src/hooks/__init__.py,sha256=iJfxk3coNJu-Rd9_QU73lEaCW6cQBE2aJiq-idAzK40,25
|
|
9
|
+
src/hooks/installer.py,sha256=nSKvigoOVtMibOiKQa3u7mz7TNGt0V_fGX0655Uf9ZY,3638
|
|
10
|
+
src/hooks/post_commit.py,sha256=pYEG9cVtZvb_NYN3rALpEGT_6IcDMbMUMMx52NvznnY,2188
|
|
11
|
+
src/pet/__init__.py,sha256=O2wViywLlqHFo5BLYsoR5eKM55N6gzWaPeNJq9zCTpE,19
|
|
12
|
+
src/pet/evolution.py,sha256=sSsBBhysL3Vw0lgZvlTH2Xt8mFmwMnz4UdbpaijpQFY,92
|
|
13
|
+
src/pet/renderer.py,sha256=1mYX787b0s2IkEz08Ylo4T4I3NiBCW0v9Sl3Di_F9hg,5440
|
|
14
|
+
src/pet/states.py,sha256=XNREk3wxkWWzzBuNHneuouQwd0mQxSv-U6JtjYPzk_Y,4352
|
|
15
|
+
src/seance/__init__.py,sha256=Dxose1-VsMTJPZgaNSzNIplDsFcNcSYEs0FWmVDw9Oo,43
|
|
16
|
+
src/seance/git_analyzer.py,sha256=vkVphOCyA5D49Dq_AXobXHopFoEtK4jnXfeYeb8O1bA,93
|
|
17
|
+
src/seance/story_gen.py,sha256=-L3xfaFw_FcRRkha7sxXLRlJnnzliCkGt-BwBYxqKRk,6659
|
|
18
|
+
src/seance/templates.py,sha256=lmdkucuSrjZXmho94gIN6qouwTh2ATnAHOE0wj1n5cQ,42
|
|
19
|
+
gitgotchi-0.1.0.dist-info/METADATA,sha256=Tg5je-Cpb-eJ8ZOyw04etNtlHRdPKwSzkgWZ9uQ1OpE,3034
|
|
20
|
+
gitgotchi-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
21
|
+
gitgotchi-0.1.0.dist-info/entry_points.txt,sha256=YU3rE0GiGuTPqgV3iy7gowLRS8Kuoe9U5h0f0hw_-DY,42
|
|
22
|
+
gitgotchi-0.1.0.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
|
|
23
|
+
gitgotchi-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 GitGotchi Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
src
|
src/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GitGotchi - A Stardew Valley-inspired terminal companion for your git repository.
|
|
3
|
+
|
|
4
|
+
Built for Kiroween Hackathon 2024.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
__author__ = "GitGotchi Team"
|
|
9
|
+
__email__ = "gitgotchi@example.com"
|
|
10
|
+
|
|
11
|
+
from src.cli import app, cli
|
|
12
|
+
|
|
13
|
+
__all__ = ["app", "cli"]
|
src/cli.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Main CLI interface."""
|
|
2
|
+
import typer
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.panel import Panel
|
|
5
|
+
|
|
6
|
+
from src.db.state_manager import StateManager
|
|
7
|
+
from src.hooks.installer import install_hooks, uninstall_hooks
|
|
8
|
+
from src.pet.renderer import render_error, render_pet
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(help="✨ GitGotchi - Your friendly dev companion")
|
|
11
|
+
console = Console()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def cli():
|
|
15
|
+
"""Entry point for console script."""
|
|
16
|
+
app()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@app.command()
|
|
20
|
+
def status():
|
|
21
|
+
"""Show your companion's current status."""
|
|
22
|
+
try:
|
|
23
|
+
state_manager = StateManager()
|
|
24
|
+
pet_stats = state_manager.get_pet_state()
|
|
25
|
+
render_pet(pet_stats, show_stats=True)
|
|
26
|
+
except Exception as e:
|
|
27
|
+
render_error(f"Failed to load companion state: {str(e)}")
|
|
28
|
+
raise typer.Exit(1)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@app.command()
|
|
32
|
+
def install():
|
|
33
|
+
"""Install GitGotchi git hooks for automatic feeding."""
|
|
34
|
+
if install_hooks():
|
|
35
|
+
console.print(
|
|
36
|
+
"\n[green]✨ Git hooks installed! Your companion will be fed automatically on commits.[/green]"
|
|
37
|
+
)
|
|
38
|
+
console.print("[dim]Make a commit to meet your new friend...[/dim]\n")
|
|
39
|
+
else:
|
|
40
|
+
raise typer.Exit(1)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@app.command()
|
|
44
|
+
def uninstall():
|
|
45
|
+
"""Remove GitGotchi git hooks."""
|
|
46
|
+
if uninstall_hooks():
|
|
47
|
+
console.print(
|
|
48
|
+
"\n[yellow]👋 Git hooks removed. You can still feed manually with 'gitgotchi feed'.[/yellow]\n"
|
|
49
|
+
)
|
|
50
|
+
else:
|
|
51
|
+
raise typer.Exit(1)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@app.command()
|
|
55
|
+
def pet():
|
|
56
|
+
"""Pet your companion (boosts mood slightly)."""
|
|
57
|
+
try:
|
|
58
|
+
state_manager = StateManager()
|
|
59
|
+
pet_stats = state_manager.get_pet_state()
|
|
60
|
+
|
|
61
|
+
# Interact with pet
|
|
62
|
+
message = pet_stats.interact()
|
|
63
|
+
|
|
64
|
+
# Save updated state
|
|
65
|
+
state_manager.save_pet_state(pet_stats)
|
|
66
|
+
|
|
67
|
+
# Show response
|
|
68
|
+
console.print(Panel(message, border_style="cyan", title="💛 Interaction"))
|
|
69
|
+
render_pet(pet_stats, show_stats=False)
|
|
70
|
+
|
|
71
|
+
except Exception as e:
|
|
72
|
+
render_error(f"Failed to interact: {str(e)}")
|
|
73
|
+
raise typer.Exit(1)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@app.command()
|
|
77
|
+
def name(new_name: str):
|
|
78
|
+
"""Give your companion a name."""
|
|
79
|
+
try:
|
|
80
|
+
state_manager = StateManager()
|
|
81
|
+
pet_stats = state_manager.get_pet_state()
|
|
82
|
+
|
|
83
|
+
old_name = pet_stats.pet_name
|
|
84
|
+
pet_stats.pet_name = new_name
|
|
85
|
+
|
|
86
|
+
state_manager.save_pet_state(pet_stats)
|
|
87
|
+
|
|
88
|
+
console.print(
|
|
89
|
+
f"\n[green]✨ Your companion is now named {new_name}![/green]"
|
|
90
|
+
)
|
|
91
|
+
if old_name != "Spirit":
|
|
92
|
+
console.print(f"[dim](Previously known as {old_name})[/dim]\n")
|
|
93
|
+
|
|
94
|
+
except Exception as e:
|
|
95
|
+
render_error(f"Failed to rename companion: {str(e)}")
|
|
96
|
+
raise typer.Exit(1)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@app.command()
|
|
100
|
+
def evolve():
|
|
101
|
+
"""Check evolution progress and requirements."""
|
|
102
|
+
try:
|
|
103
|
+
state_manager = StateManager()
|
|
104
|
+
pet_stats = state_manager.get_pet_state()
|
|
105
|
+
|
|
106
|
+
# Calculate progress
|
|
107
|
+
current_form = pet_stats.current_form.value
|
|
108
|
+
commits = pet_stats.total_commits
|
|
109
|
+
quality = pet_stats.quality_score
|
|
110
|
+
|
|
111
|
+
console.print(
|
|
112
|
+
Panel(
|
|
113
|
+
f"""[cyan]Current Form:[/cyan] {current_form}
|
|
114
|
+
[cyan]Total Commits:[/cyan] {commits}
|
|
115
|
+
[cyan]Quality Score:[/cyan] {quality:.1f}/100
|
|
116
|
+
[cyan]Friend Level:[/cyan] {"❤️" * int(pet_stats.friend_level)} ({pet_stats.friend_level}/10)
|
|
117
|
+
|
|
118
|
+
[yellow]Evolution Requirements:[/yellow]
|
|
119
|
+
• Egg → Ghost: 3 commits (you have {commits})
|
|
120
|
+
• Ghost → Angel: 6 commits + quality > 70
|
|
121
|
+
• Ghost → Demon: 6 commits + quality < 30
|
|
122
|
+
• Any → Zombie: 4 reverts (you have {pet_stats.reverts})
|
|
123
|
+
• Any → Wraith: 15 commits (ultimate form!)
|
|
124
|
+
""",
|
|
125
|
+
title="🌟 Evolution Progress",
|
|
126
|
+
border_style="cyan",
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
except Exception as e:
|
|
131
|
+
render_error(f"Failed to check evolution: {str(e)}")
|
|
132
|
+
raise typer.Exit(1)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@app.command()
|
|
136
|
+
def story():
|
|
137
|
+
"""Hear a story about your code's history."""
|
|
138
|
+
try:
|
|
139
|
+
from src.seance.story_gen import StoryGenerator
|
|
140
|
+
|
|
141
|
+
state_manager = StateManager()
|
|
142
|
+
pet_stats = state_manager.get_pet_state()
|
|
143
|
+
|
|
144
|
+
generator = StoryGenerator()
|
|
145
|
+
|
|
146
|
+
# Generate milestone story
|
|
147
|
+
context = {
|
|
148
|
+
"total_commits": pet_stats.total_commits,
|
|
149
|
+
"total_lines": pet_stats.lines_added + pet_stats.lines_deleted,
|
|
150
|
+
"days_together": (
|
|
151
|
+
pet_stats.last_fed - pet_stats.last_fed
|
|
152
|
+
).days, # Simplified
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
story_text = generator.generate_story(
|
|
156
|
+
"milestone", context, pet_stats.current_form.value
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
console.print(
|
|
160
|
+
Panel(
|
|
161
|
+
story_text,
|
|
162
|
+
title=f"📖 {pet_stats.pet_name}'s Story",
|
|
163
|
+
border_style="cyan",
|
|
164
|
+
padding=(1, 2),
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
except Exception as e:
|
|
169
|
+
render_error(f"Failed to generate story: {str(e)}")
|
|
170
|
+
raise typer.Exit(1)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@app.command()
|
|
174
|
+
def version():
|
|
175
|
+
"""Show GitGotchi version."""
|
|
176
|
+
console.print(
|
|
177
|
+
"GitGotchi v0.1.0 - Your friendly dev companion ✨", style="bold cyan"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
if __name__ == "__main__":
|
|
182
|
+
app()
|
src/db/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Database module."""
|
src/db/migrations.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Database migration utilities."""
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from sqlalchemy import text
|
|
6
|
+
from sqlalchemy.orm import Session
|
|
7
|
+
|
|
8
|
+
from src.db.models import get_engine
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MigrationManager:
|
|
12
|
+
"""Handles database schema migrations."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, db_path: str = ".gitgotchi/state.db") -> None:
|
|
15
|
+
"""Initialize migration manager.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
db_path: Path to SQLite database file
|
|
19
|
+
"""
|
|
20
|
+
self.db_path = db_path
|
|
21
|
+
self.engine = get_engine(db_path)
|
|
22
|
+
|
|
23
|
+
def get_schema_version(self) -> int:
|
|
24
|
+
"""Get current schema version.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Schema version number, 0 if not set
|
|
28
|
+
"""
|
|
29
|
+
try:
|
|
30
|
+
with Session(self.engine) as session:
|
|
31
|
+
result = session.execute(
|
|
32
|
+
text("SELECT version FROM schema_version LIMIT 1")
|
|
33
|
+
)
|
|
34
|
+
row = result.fetchone()
|
|
35
|
+
return row[0] if row else 0
|
|
36
|
+
except Exception:
|
|
37
|
+
return 0
|
|
38
|
+
|
|
39
|
+
def set_schema_version(self, version: int) -> None:
|
|
40
|
+
"""Set schema version.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
version: Version number to set
|
|
44
|
+
"""
|
|
45
|
+
with Session(self.engine) as session:
|
|
46
|
+
# Create table if not exists
|
|
47
|
+
session.execute(
|
|
48
|
+
text(
|
|
49
|
+
"""
|
|
50
|
+
CREATE TABLE IF NOT EXISTS schema_version (
|
|
51
|
+
version INTEGER PRIMARY KEY
|
|
52
|
+
)
|
|
53
|
+
"""
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Update or insert version
|
|
58
|
+
session.execute(text("DELETE FROM schema_version"))
|
|
59
|
+
session.execute(
|
|
60
|
+
text("INSERT INTO schema_version (version) VALUES (:version)"),
|
|
61
|
+
{"version": version},
|
|
62
|
+
)
|
|
63
|
+
session.commit()
|
|
64
|
+
|
|
65
|
+
def backup_database(self) -> Optional[str]:
|
|
66
|
+
"""Create backup of database.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Path to backup file if successful, None otherwise
|
|
70
|
+
"""
|
|
71
|
+
try:
|
|
72
|
+
db_file = Path(self.db_path)
|
|
73
|
+
if not db_file.exists():
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
backup_path = db_file.with_suffix(".db.backup")
|
|
77
|
+
backup_path.write_bytes(db_file.read_bytes())
|
|
78
|
+
return str(backup_path)
|
|
79
|
+
except Exception:
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
def run_migrations(self) -> None:
|
|
83
|
+
"""Run all pending migrations."""
|
|
84
|
+
current_version = self.get_schema_version()
|
|
85
|
+
|
|
86
|
+
# Define migrations
|
|
87
|
+
migrations = [
|
|
88
|
+
self._migration_v1_initial,
|
|
89
|
+
# Add future migrations here
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
for version, migration in enumerate(migrations, start=1):
|
|
93
|
+
if version > current_version:
|
|
94
|
+
self.backup_database()
|
|
95
|
+
migration()
|
|
96
|
+
self.set_schema_version(version)
|
|
97
|
+
|
|
98
|
+
def _migration_v1_initial(self) -> None:
|
|
99
|
+
"""Initial schema - handled by SQLAlchemy create_all."""
|
|
100
|
+
pass
|
src/db/models.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""SQLite schemas."""
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from sqlalchemy import Float, Integer, String, Text, create_engine
|
|
6
|
+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Base(DeclarativeBase):
|
|
10
|
+
"""Base class for all models."""
|
|
11
|
+
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PetState(Base):
|
|
16
|
+
"""Singleton table storing current pet state (one row per repo)."""
|
|
17
|
+
|
|
18
|
+
__tablename__ = "pet_state"
|
|
19
|
+
|
|
20
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
21
|
+
pet_name: Mapped[str] = mapped_column(String(100), default="Spirit")
|
|
22
|
+
total_commits: Mapped[int] = mapped_column(Integer, default=0)
|
|
23
|
+
lines_added: Mapped[int] = mapped_column(Integer, default=0)
|
|
24
|
+
lines_deleted: Mapped[int] = mapped_column(Integer, default=0)
|
|
25
|
+
merge_conflicts: Mapped[int] = mapped_column(Integer, default=0)
|
|
26
|
+
reverts: Mapped[int] = mapped_column(Integer, default=0)
|
|
27
|
+
quality_score: Mapped[float] = mapped_column(Float, default=50.0)
|
|
28
|
+
evolution_points: Mapped[int] = mapped_column(Integer, default=0)
|
|
29
|
+
friend_level: Mapped[float] = mapped_column(Float, default=1.0)
|
|
30
|
+
last_fed: Mapped[datetime] = mapped_column(default=datetime.now)
|
|
31
|
+
current_mood: Mapped[str] = mapped_column(String(20), default="content")
|
|
32
|
+
current_form: Mapped[str] = mapped_column(String(20), default="egg")
|
|
33
|
+
created_at: Mapped[datetime] = mapped_column(default=datetime.now)
|
|
34
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
35
|
+
default=datetime.now, onupdate=datetime.now
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class CommitHistory(Base):
|
|
40
|
+
"""Log of all tracked commits."""
|
|
41
|
+
|
|
42
|
+
__tablename__ = "commit_history"
|
|
43
|
+
|
|
44
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
45
|
+
commit_hash: Mapped[str] = mapped_column(String(40), unique=True, index=True)
|
|
46
|
+
author: Mapped[str] = mapped_column(String(255))
|
|
47
|
+
message: Mapped[str] = mapped_column(Text)
|
|
48
|
+
timestamp: Mapped[datetime] = mapped_column(index=True)
|
|
49
|
+
lines_added: Mapped[int] = mapped_column(Integer, default=0)
|
|
50
|
+
lines_deleted: Mapped[int] = mapped_column(Integer, default=0)
|
|
51
|
+
files_changed: Mapped[int] = mapped_column(Integer, default=0)
|
|
52
|
+
is_merge: Mapped[bool] = mapped_column(default=False)
|
|
53
|
+
is_revert: Mapped[bool] = mapped_column(default=False)
|
|
54
|
+
quality_impact: Mapped[float] = mapped_column(Float, default=0.0)
|
|
55
|
+
processed_at: Mapped[datetime] = mapped_column(default=datetime.now)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class StoryMemory(Base):
|
|
59
|
+
"""Cache of generated stories."""
|
|
60
|
+
|
|
61
|
+
__tablename__ = "story_memory"
|
|
62
|
+
|
|
63
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
64
|
+
commit_hash: Mapped[str] = mapped_column(String(40), index=True)
|
|
65
|
+
story_text: Mapped[str] = mapped_column(Text)
|
|
66
|
+
story_type: Mapped[str] = mapped_column(String(50))
|
|
67
|
+
pet_form_at_time: Mapped[str] = mapped_column(String(20))
|
|
68
|
+
pet_mood_at_time: Mapped[str] = mapped_column(String(20))
|
|
69
|
+
generated_at: Mapped[datetime] = mapped_column(default=datetime.now)
|
|
70
|
+
tokens_used: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_engine(db_path: str = ".gitgotchi/state.db"):
|
|
74
|
+
"""Create database engine.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
db_path: Path to SQLite database file
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
SQLAlchemy engine instance
|
|
81
|
+
"""
|
|
82
|
+
return create_engine(f"sqlite:///{db_path}", echo=False)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def init_db(db_path: str = ".gitgotchi/state.db") -> None:
|
|
86
|
+
"""Initialize database schema.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
db_path: Path to SQLite database file
|
|
90
|
+
"""
|
|
91
|
+
engine = get_engine(db_path)
|
|
92
|
+
Base.metadata.create_all(engine)
|