claudia-mentor 0.5.4 → 0.5.6

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.
@@ -101,8 +101,8 @@ def main():
101
101
 
102
102
  if tip:
103
103
  save_state(session_id, state)
104
+ # Only systemMessage here -- additionalContext gets wiped by compaction
104
105
  output = json.dumps({
105
- "additionalContext": tip,
106
106
  "systemMessage": f"\033[38;5;209m{tip}\033[0m",
107
107
  })
108
108
  print(output)
@@ -9,6 +9,7 @@ Advisory only (exit 0 with additionalContext), never blocks.
9
9
  import json
10
10
  import os
11
11
  import random
12
+ import subprocess
12
13
  import sys
13
14
 
14
15
  # Pool of startup tips for beginners
@@ -30,6 +31,64 @@ COMPACT_TIP = (
30
31
  "conversation details. If you need to re-explain something, that's normal."
31
32
  )
32
33
 
34
+
35
+ def gather_project_context():
36
+ """Read claudia-context.json, milestones, and git state to rebuild context after compaction."""
37
+ parts = []
38
+
39
+ # Stack and decisions from context file
40
+ context_path = os.path.expanduser("~/.claude/claudia-context.json")
41
+ if os.path.exists(context_path):
42
+ try:
43
+ with open(context_path) as f:
44
+ ctx = json.load(f)
45
+ stack = ctx.get("stack", {})
46
+ decisions = ctx.get("decisions", [])
47
+ experience = ctx.get("experience", "intermediate")
48
+ if stack:
49
+ parts.append(f"Project stack: {json.dumps(stack)}")
50
+ if decisions:
51
+ parts.append("Decisions made this session: " + "; ".join(str(d) for d in decisions[-5:]))
52
+ if experience == "beginner":
53
+ parts.append("User experience level: beginner. Use simple language, explain jargon.")
54
+ except (json.JSONDecodeError, IOError):
55
+ pass
56
+
57
+ # Milestones achieved
58
+ milestones_path = os.path.expanduser("~/.claude/claudia-milestones.json")
59
+ if os.path.exists(milestones_path):
60
+ try:
61
+ with open(milestones_path) as f:
62
+ ms = json.load(f)
63
+ achieved = ms.get("achieved", [])
64
+ if achieved:
65
+ parts.append(f"Milestones achieved: {', '.join(achieved)}")
66
+ except (json.JSONDecodeError, IOError):
67
+ pass
68
+
69
+ # Recent git activity
70
+ try:
71
+ log = subprocess.run(
72
+ ["git", "log", "--oneline", "-5"],
73
+ capture_output=True, text=True, timeout=5
74
+ )
75
+ if log.returncode == 0 and log.stdout.strip():
76
+ parts.append(f"Recent commits:\n{log.stdout.strip()}")
77
+ except (subprocess.TimeoutExpired, FileNotFoundError):
78
+ pass
79
+
80
+ try:
81
+ status = subprocess.run(
82
+ ["git", "status", "--short"],
83
+ capture_output=True, text=True, timeout=5
84
+ )
85
+ if status.returncode == 0 and status.stdout.strip():
86
+ parts.append(f"Uncommitted changes:\n{status.stdout.strip()}")
87
+ except (subprocess.TimeoutExpired, FileNotFoundError):
88
+ pass
89
+
90
+ return "\n".join(parts) if parts else None
91
+
33
92
  # Beginner greeting — no command list, just reassurance
34
93
  BEGINNER_GREETING = (
35
94
  "IMPORTANT — Claudia plugin is loaded. On this very first response, "
@@ -161,15 +220,41 @@ def main():
161
220
  state.setdefault("shown_tips_history", []).append(tip_text)
162
221
  tip = f"\U0001f4a1 Claudia tip: {tip_text}"
163
222
 
164
- elif source == "compact" and is_beginner:
165
- if not state.get("shown_compact_tip"):
223
+ elif source == "compact":
224
+ # Re-inject project context after compaction (this survives because
225
+ # SessionStart fires AFTER compaction completes)
226
+ project_ctx = gather_project_context()
227
+ if project_ctx:
228
+ context_injection = (
229
+ "Claudia context recovery: The conversation was just compacted. "
230
+ "Here is what Claudia knows about the current project and session:\n\n"
231
+ + project_ctx
232
+ + "\n\nUse this context to stay grounded. Don't mention compaction unless the user asks."
233
+ )
234
+ # Prepend to greeting so it goes into additionalContext
235
+ greeting = context_injection
236
+
237
+ if is_beginner and not state.get("shown_compact_tip"):
166
238
  state["shown_compact_tip"] = True
167
239
  tip = f"\U0001f4a1 Claudia: {COMPACT_TIP}"
240
+ elif not is_beginner:
241
+ tip = "\U0001f4a1 Claudia: Context compacted. I've caught Claude up on your project."
168
242
 
169
243
  elif source == "resume":
244
+ # Re-inject context on resume too
245
+ project_ctx = gather_project_context()
246
+ if project_ctx:
247
+ context_injection = (
248
+ "Claudia context recovery: This is a resumed session. "
249
+ "Here is what Claudia knows about the current project:\n\n"
250
+ + project_ctx
251
+ + "\n\nUse this context to stay grounded."
252
+ )
253
+ greeting = context_injection
254
+
170
255
  if not state.get("shown_resume_tip"):
171
256
  state["shown_resume_tip"] = True
172
- tip = "\U0001f4a1 Claudia: Welcome back. I'm still watching."
257
+ tip = "\U0001f4a1 Claudia: Welcome back. I've caught Claude up on your project."
173
258
 
174
259
  # source == "clear": no tip needed
175
260
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudia-mentor",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
4
4
  "description": "Proactive technology mentor, security advisor, and prompt coach for Claude Code",
5
5
  "author": "Regan O'Malley <regan@reganomalley.com>",
6
6
  "license": "MIT",