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
src/pet/states.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Pet state machine."""
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PetMood(Enum):
|
|
7
|
+
"""Pet mood states - Stardew Valley inspired friendly moods."""
|
|
8
|
+
|
|
9
|
+
JOYFUL = "joyful" # Recent excellent commit
|
|
10
|
+
HAPPY = "happy" # Healthy regular activity
|
|
11
|
+
CONTENT = "content" # Baseline, all is well
|
|
12
|
+
HUNGRY = "hungry" # No commits in 24h
|
|
13
|
+
TIRED = "tired" # No commits in 3 days
|
|
14
|
+
SLEEPING = "sleeping" # No commits in 7 days (can be woken)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PetForm(Enum):
|
|
18
|
+
"""Pet evolution stages - FAST progression for quick feedback."""
|
|
19
|
+
|
|
20
|
+
EGG = "egg" # 0-2 commits
|
|
21
|
+
GHOST = "ghost" # 3-5 commits (default friendly spirit)
|
|
22
|
+
ANGEL = "angel" # 6+ commits, quality > 70
|
|
23
|
+
DEMON = "demon" # 6+ commits, quality < 30 (playful purple, not evil)
|
|
24
|
+
ZOMBIE = "zombie" # 4+ reverts (cute sleepy, needs coffee)
|
|
25
|
+
WRAITH = "wraith" # 15+ commits (ultimate ethereal form)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class PetStats:
|
|
29
|
+
"""Pet statistics and state."""
|
|
30
|
+
|
|
31
|
+
def __init__(self) -> None:
|
|
32
|
+
"""Initialize pet with default stats."""
|
|
33
|
+
self.total_commits: int = 0
|
|
34
|
+
self.lines_added: int = 0
|
|
35
|
+
self.lines_deleted: int = 0
|
|
36
|
+
self.merge_conflicts: int = 0
|
|
37
|
+
self.reverts: int = 0
|
|
38
|
+
self.quality_score: float = 50.0
|
|
39
|
+
self.last_fed: datetime = datetime.now()
|
|
40
|
+
self.current_mood: PetMood = PetMood.CONTENT
|
|
41
|
+
self.current_form: PetForm = PetForm.EGG
|
|
42
|
+
self.evolution_points: int = 0
|
|
43
|
+
self.friend_level: int = 1 # 1-10, Stardew Valley style
|
|
44
|
+
self.pet_name: str = "Spirit"
|
|
45
|
+
|
|
46
|
+
def update_mood(self) -> None:
|
|
47
|
+
"""Update pet mood based on current stats and time."""
|
|
48
|
+
hours_since_fed = (datetime.now() - self.last_fed).total_seconds() / 3600
|
|
49
|
+
|
|
50
|
+
if hours_since_fed > 168: # 7 days
|
|
51
|
+
self.current_mood = PetMood.SLEEPING
|
|
52
|
+
elif hours_since_fed > 72: # 3 days
|
|
53
|
+
self.current_mood = PetMood.TIRED
|
|
54
|
+
elif hours_since_fed > 24: # 1 day
|
|
55
|
+
self.current_mood = PetMood.HUNGRY
|
|
56
|
+
elif self.quality_score > 80:
|
|
57
|
+
self.current_mood = PetMood.JOYFUL
|
|
58
|
+
elif self.quality_score > 60:
|
|
59
|
+
self.current_mood = PetMood.HAPPY
|
|
60
|
+
else:
|
|
61
|
+
self.current_mood = PetMood.CONTENT
|
|
62
|
+
|
|
63
|
+
def update_form(self) -> None:
|
|
64
|
+
"""Update pet form - FAST evolution for quick feedback."""
|
|
65
|
+
# Fast progression: egg → ghost in 2-3 commits
|
|
66
|
+
if self.total_commits <= 2:
|
|
67
|
+
self.current_form = PetForm.EGG
|
|
68
|
+
# Zombie path: lots of reverts (cute sleepy form)
|
|
69
|
+
elif self.reverts >= 4:
|
|
70
|
+
self.current_form = PetForm.ZOMBIE
|
|
71
|
+
# Ultimate form: 15+ commits
|
|
72
|
+
elif self.total_commits >= 15:
|
|
73
|
+
self.current_form = PetForm.WRAITH
|
|
74
|
+
# Advanced forms: 6+ commits
|
|
75
|
+
elif self.total_commits >= 6:
|
|
76
|
+
if self.quality_score > 70:
|
|
77
|
+
self.current_form = PetForm.ANGEL
|
|
78
|
+
elif self.quality_score < 30:
|
|
79
|
+
self.current_form = PetForm.DEMON
|
|
80
|
+
else:
|
|
81
|
+
self.current_form = PetForm.GHOST
|
|
82
|
+
# Default friendly spirit: 3-5 commits
|
|
83
|
+
else:
|
|
84
|
+
self.current_form = PetForm.GHOST
|
|
85
|
+
|
|
86
|
+
def feed(self) -> None:
|
|
87
|
+
"""Feed the pet (update last_fed timestamp)."""
|
|
88
|
+
self.last_fed = datetime.now()
|
|
89
|
+
self.update_mood()
|
|
90
|
+
|
|
91
|
+
def interact(self) -> str:
|
|
92
|
+
"""Handle user interaction (pet, talk, play)."""
|
|
93
|
+
# Wake up if sleeping
|
|
94
|
+
if self.current_mood == PetMood.SLEEPING:
|
|
95
|
+
self.current_mood = PetMood.CONTENT
|
|
96
|
+
return "Your friend wakes up! They're happy to see you! ✨"
|
|
97
|
+
|
|
98
|
+
# Improve mood slightly
|
|
99
|
+
if self.current_mood == PetMood.TIRED:
|
|
100
|
+
self.current_mood = PetMood.CONTENT
|
|
101
|
+
elif self.current_mood == PetMood.HUNGRY:
|
|
102
|
+
self.current_mood = PetMood.CONTENT
|
|
103
|
+
|
|
104
|
+
# Increase friendship
|
|
105
|
+
self.friend_level = min(10, self.friend_level + 0.5)
|
|
106
|
+
|
|
107
|
+
responses = [
|
|
108
|
+
"Your friend sparkles with joy! ✨",
|
|
109
|
+
"They float happily around you! 💛",
|
|
110
|
+
"Your companion feels loved! 🌟",
|
|
111
|
+
"Friendship +0.5! Your bond grows stronger! ❤️",
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
import random
|
|
115
|
+
|
|
116
|
+
return random.choice(responses)
|
src/seance/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Seance module for story generation."""
|
src/seance/story_gen.py
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""LLM story generation with Claude."""
|
|
2
|
+
import os
|
|
3
|
+
import random
|
|
4
|
+
from typing import Dict, Optional
|
|
5
|
+
|
|
6
|
+
from anthropic import Anthropic
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class StoryGenerator:
|
|
10
|
+
"""Generate warm, friendly stories about code history using Claude AI."""
|
|
11
|
+
|
|
12
|
+
SYSTEM_PROMPT = """You are a friendly spirit companion from a cozy pixel game
|
|
13
|
+
like Stardew Valley. You live in a developer's git repository and tell warm,
|
|
14
|
+
encouraging stories about their code history.
|
|
15
|
+
|
|
16
|
+
Your personality:
|
|
17
|
+
- Warm and supportive (never critical or scary)
|
|
18
|
+
- Playful and gentle (use sparkles ✨, hearts ❤️, leaves 🍂)
|
|
19
|
+
- Wise but humble (you're learning about code alongside them)
|
|
20
|
+
- Encouraging (celebrate all progress, big and small)
|
|
21
|
+
|
|
22
|
+
Your stories should:
|
|
23
|
+
- Be 2-4 short paragraphs
|
|
24
|
+
- Include specific details from the code history
|
|
25
|
+
- End with a gentle question or encouragement
|
|
26
|
+
- Use soft, magical language
|
|
27
|
+
- Make the developer feel good about their work
|
|
28
|
+
|
|
29
|
+
Avoid:
|
|
30
|
+
- Technical jargon (keep it accessible)
|
|
31
|
+
- Criticism or judgment
|
|
32
|
+
- Scary or dark themes
|
|
33
|
+
- Overwhelming detail
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, api_key: Optional[str] = None):
|
|
37
|
+
"""Initialize story generator.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
api_key: Anthropic API key (or from ANTHROPIC_API_KEY env var)
|
|
41
|
+
"""
|
|
42
|
+
self.api_key = api_key or os.getenv("ANTHROPIC_API_KEY")
|
|
43
|
+
self.client = Anthropic(api_key=self.api_key) if self.api_key else None
|
|
44
|
+
|
|
45
|
+
def generate_story(
|
|
46
|
+
self, story_type: str, context: Dict, pet_form: str
|
|
47
|
+
) -> str:
|
|
48
|
+
"""Generate a story using Claude.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
story_type: Type of story (forgotten_todo, milestone, etc.)
|
|
52
|
+
context: Relevant git data
|
|
53
|
+
pet_form: Current companion form
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Friendly story text (2-4 paragraphs)
|
|
57
|
+
"""
|
|
58
|
+
if not self.client:
|
|
59
|
+
return self._get_fallback_story(story_type)
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
prompt = self._build_story_prompt(story_type, context, pet_form)
|
|
63
|
+
|
|
64
|
+
message = self.client.messages.create(
|
|
65
|
+
model="claude-3-5-sonnet-20241022",
|
|
66
|
+
max_tokens=500,
|
|
67
|
+
system=self.SYSTEM_PROMPT,
|
|
68
|
+
messages=[{"role": "user", "content": prompt}],
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return message.content[0].text
|
|
72
|
+
|
|
73
|
+
except Exception:
|
|
74
|
+
return self._get_fallback_story(story_type)
|
|
75
|
+
|
|
76
|
+
def get_daily_message(self, state: Dict) -> str:
|
|
77
|
+
"""Get a warm daily message based on current state.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
state: Current pet state
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Encouraging message
|
|
84
|
+
"""
|
|
85
|
+
messages = [
|
|
86
|
+
"Good morning! Ready to write some code together? ✨",
|
|
87
|
+
"Hello friend! What shall we build today? 🌟",
|
|
88
|
+
"A new day, a fresh start! I'm excited to see what you create! 💛",
|
|
89
|
+
"Your companion is here and ready to help! 🍂",
|
|
90
|
+
"Time to make some magic happen! ✨",
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
return random.choice(messages)
|
|
94
|
+
|
|
95
|
+
def get_evolution_message(self, old_form: str, new_form: str) -> str:
|
|
96
|
+
"""Special message for evolution events.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
old_form: Previous form
|
|
100
|
+
new_form: New form
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Celebratory message
|
|
104
|
+
"""
|
|
105
|
+
messages = {
|
|
106
|
+
("egg", "ghost"): "Your friend has hatched! Welcome to the world, little spirit! ✨",
|
|
107
|
+
("ghost", "angel"): "Your dedication shines bright! Your companion has become an angel! 👼",
|
|
108
|
+
("ghost", "demon"): "Chaos can be fun! Your companion embraces their playful side! 😈💜",
|
|
109
|
+
("ghost", "zombie"): "Your friend needs coffee! They've become a sleepy zombie! ☕💚",
|
|
110
|
+
"wraith": "Ultimate form achieved! Your companion is now an ethereal wraith! 🌙✨",
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
key = (old_form, new_form)
|
|
114
|
+
if key in messages:
|
|
115
|
+
return messages[key]
|
|
116
|
+
elif new_form == "wraith":
|
|
117
|
+
return messages["wraith"]
|
|
118
|
+
|
|
119
|
+
return f"Your {old_form} evolved into a {new_form}! How wonderful! 🎉"
|
|
120
|
+
|
|
121
|
+
def _build_story_prompt(
|
|
122
|
+
self, story_type: str, context: Dict, pet_form: str
|
|
123
|
+
) -> str:
|
|
124
|
+
"""Build prompt for Claude based on story type and context."""
|
|
125
|
+
prompts = {
|
|
126
|
+
"milestone": f"""
|
|
127
|
+
Tell a celebratory story about reaching a coding milestone.
|
|
128
|
+
|
|
129
|
+
Details:
|
|
130
|
+
- Total commits: {context.get('total_commits', 0)}
|
|
131
|
+
- Lines of code: {context.get('total_lines', 0)}
|
|
132
|
+
- Time coding together: {context.get('days_together', 0)} days
|
|
133
|
+
|
|
134
|
+
Make it feel like a celebration of progress and friendship.
|
|
135
|
+
End with encouragement for the journey ahead.
|
|
136
|
+
""",
|
|
137
|
+
"first_commit": f"""
|
|
138
|
+
Tell a warm welcome story about the first commit in this repository.
|
|
139
|
+
|
|
140
|
+
Details:
|
|
141
|
+
- Commit message: "{context.get('message', '')}"
|
|
142
|
+
- Date: {context.get('date', 'recently')}
|
|
143
|
+
|
|
144
|
+
Make it feel like the beginning of a wonderful adventure together.
|
|
145
|
+
""",
|
|
146
|
+
"encouragement": """
|
|
147
|
+
Tell a gentle, encouraging story about the value of consistent effort.
|
|
148
|
+
|
|
149
|
+
Make the developer feel appreciated for their work, no matter how small.
|
|
150
|
+
Remind them that every commit is progress.
|
|
151
|
+
""",
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return prompts.get(story_type, prompts["encouragement"])
|
|
155
|
+
|
|
156
|
+
def _get_fallback_story(self, story_type: str) -> str:
|
|
157
|
+
"""Return pre-written story when API unavailable."""
|
|
158
|
+
fallback_stories = {
|
|
159
|
+
"milestone": [
|
|
160
|
+
"Look how far we've come together! Every commit is a step forward, and you've taken so many. I'm proud to be your companion on this journey! 🌟",
|
|
161
|
+
"Congratulations on all your hard work! From that first commit to now, we've built something special together. Here's to many more adventures! ✨",
|
|
162
|
+
],
|
|
163
|
+
"encouragement": [
|
|
164
|
+
"Every line of code is a step forward. You're doing great! 💛",
|
|
165
|
+
"Progress isn't always visible, but I see how hard you work! Keep going! ✨",
|
|
166
|
+
"Even small commits make a difference. Your dedication inspires me! 🌟",
|
|
167
|
+
],
|
|
168
|
+
"first_commit": [
|
|
169
|
+
"Welcome! This is the beginning of something wonderful. I'm so happy to be here with you! ✨",
|
|
170
|
+
"A new adventure begins! Every great project starts with a single commit. Let's build something amazing together! 💛",
|
|
171
|
+
],
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
stories = fallback_stories.get(
|
|
175
|
+
story_type, fallback_stories["encouragement"]
|
|
176
|
+
)
|
|
177
|
+
return random.choice(stories)
|
src/seance/templates.py
ADDED