claudia-mentor 0.8.0 → 0.9.0
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.
- package/README.md +12 -4
- package/hooks/scripts/claudia-compact-tip.py +5 -1
- package/hooks/scripts/claudia-milestones.py +3 -2
- package/hooks/scripts/claudia-next-steps.py +34 -21
- package/hooks/scripts/claudia-prompt-coach.py +12 -3
- package/hooks/scripts/claudia-run-suggest.py +7 -3
- package/hooks/scripts/claudia-session-tips.py +5 -1
- package/hooks/scripts/claudia-stop-dispatch.py +14 -1
- package/hooks/scripts/claudia-teach.py +19 -4
- package/hooks/scripts/claudia_config.py +58 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
A Claude Code plugin that acts as your technology mentor, security advisor, and prompt coach. Claudia fills the gaps between writing code and making good technology decisions.
|
|
6
6
|
|
|
7
|
-
**10 knowledge domains.
|
|
7
|
+
**10 knowledge domains. 15 hooks. 11 commands. 264 tests. Beginner-friendly.**
|
|
8
8
|
|
|
9
9
|
## What Claudia Does
|
|
10
10
|
|
|
@@ -60,7 +60,7 @@ Claudia automatically activates when you:
|
|
|
60
60
|
|
|
61
61
|
### Hooks (Always Active)
|
|
62
62
|
|
|
63
|
-
**
|
|
63
|
+
**8 file-check hooks** (run on every file write):
|
|
64
64
|
|
|
65
65
|
| Hook | Type | What it catches |
|
|
66
66
|
|------|------|-----------------|
|
|
@@ -71,6 +71,7 @@ Claudia automatically activates when you:
|
|
|
71
71
|
| Git hygiene | blocks | .env writes, merge conflict markers. Warns on large binaries |
|
|
72
72
|
| Accessibility | warns | Missing alt text, unlabeled inputs, icon-only buttons, div click handlers |
|
|
73
73
|
| License compliance | warns | GPL/AGPL dependencies in permissive-licensed projects |
|
|
74
|
+
| CSS anti-patterns | warns | `!important` overuse, magic numbers, deep nesting, inline styles in templates |
|
|
74
75
|
|
|
75
76
|
**7 proactive hooks** (watch your conversation):
|
|
76
77
|
|
|
@@ -86,7 +87,7 @@ Claudia automatically activates when you:
|
|
|
86
87
|
|
|
87
88
|
## Beginner Mode
|
|
88
89
|
|
|
89
|
-
Set `"experience": "beginner"` in `~/.claude/claudia
|
|
90
|
+
Set `"experience": "beginner"` in `~/.claude/claudia.json` (or `~/.claude/claudia-context.json`, or run `/claudia:setup`) and Claudia adapts:
|
|
90
91
|
|
|
91
92
|
- **Simplified greeting** -- No command list on startup. Just "Claudia is here. Just build. I'm watching."
|
|
92
93
|
- **Stuck detection** -- Type "I'm stuck" or "help" and she asks one clarifying question, then suggests one small next step
|
|
@@ -165,12 +166,19 @@ Override Claudia's personality and proactivity level:
|
|
|
165
166
|
```json
|
|
166
167
|
{
|
|
167
168
|
"proactivity": "high",
|
|
169
|
+
"experience": "beginner",
|
|
168
170
|
"personality": {
|
|
169
171
|
"tone": "casual"
|
|
170
|
-
}
|
|
172
|
+
},
|
|
173
|
+
"suppress_topics": ["Netlify", "hosting"],
|
|
174
|
+
"suppress_hooks": ["next-steps", "milestones"]
|
|
171
175
|
}
|
|
172
176
|
```
|
|
173
177
|
|
|
178
|
+
`suppress_topics` silences teach tips for specific keywords (e.g. `"Netlify"`) or entire categories (e.g. `"hosting"`). Case-insensitive. Each tip includes a dismiss hint showing how to add it.
|
|
179
|
+
|
|
180
|
+
`suppress_hooks` silences entire proactive hooks by name. Valid names: `teach`, `prompt-coach`, `next-steps`, `run-suggest`, `milestones`, `session-tips`, `compact-tip`. Each hook occasionally shows a dismiss hint telling you how.
|
|
181
|
+
|
|
174
182
|
### Per-project (`.claudia.json` in project root)
|
|
175
183
|
|
|
176
184
|
```json
|
|
@@ -37,7 +37,7 @@ def save_state(session_id, state):
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
40
|
-
from claudia_config import load_user_config
|
|
40
|
+
from claudia_config import load_user_config, load_suppress_hooks
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
def load_config():
|
|
@@ -56,6 +56,10 @@ def main():
|
|
|
56
56
|
proactivity, experience = load_config()
|
|
57
57
|
is_beginner = experience == "beginner"
|
|
58
58
|
|
|
59
|
+
# Bail if hook is suppressed
|
|
60
|
+
if "compact-tip" in load_suppress_hooks():
|
|
61
|
+
sys.exit(0)
|
|
62
|
+
|
|
59
63
|
# Non-beginners at moderate or low proactivity: skip entirely
|
|
60
64
|
if not is_beginner:
|
|
61
65
|
sys.exit(0)
|
|
@@ -94,7 +94,7 @@ def save_state(state):
|
|
|
94
94
|
|
|
95
95
|
|
|
96
96
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
97
|
-
from claudia_config import load_user_config
|
|
97
|
+
from claudia_config import load_user_config, dismiss_hint
|
|
98
98
|
|
|
99
99
|
|
|
100
100
|
def load_config():
|
|
@@ -156,7 +156,8 @@ def check(input_data, proactivity, experience):
|
|
|
156
156
|
state["achieved"] = list(achieved)
|
|
157
157
|
save_state(state)
|
|
158
158
|
msg = f"Claudia: {celebration}"
|
|
159
|
-
|
|
159
|
+
user_hint, claude_hint = dismiss_hint("milestones")
|
|
160
|
+
return {"additionalContext": msg + "\n" + claude_hint, "systemMessage": f"\033[38;5;160m{msg}\n{user_hint}\033[0m"}
|
|
160
161
|
|
|
161
162
|
# Save state even without celebration (for file_count tracking)
|
|
162
163
|
if new_files > 0:
|
|
@@ -14,25 +14,28 @@ import time
|
|
|
14
14
|
|
|
15
15
|
# Completion signals
|
|
16
16
|
COMPLETION_PATTERNS = [
|
|
17
|
-
r"I've created",
|
|
18
|
-
r"I have created",
|
|
19
|
-
r"I've written",
|
|
20
|
-
r"I've built",
|
|
21
|
-
r"I've set up",
|
|
22
|
-
r"I've added",
|
|
23
|
-
r"I've updated",
|
|
24
|
-
r"I've fixed",
|
|
25
|
-
r"I've implemented",
|
|
26
|
-
r"Done[.!]",
|
|
27
|
-
r"Here's your",
|
|
28
|
-
r"Here is your",
|
|
29
|
-
r"All set[.!]",
|
|
30
|
-
r"That's done",
|
|
31
|
-
r"It's ready",
|
|
32
|
-
r"The [\w\s]+ is ready",
|
|
33
|
-
r"Your [\w\s]+ is ready",
|
|
17
|
+
r"^I've created",
|
|
18
|
+
r"^I have created",
|
|
19
|
+
r"^I've written",
|
|
20
|
+
r"^I've built",
|
|
21
|
+
r"^I've set up",
|
|
22
|
+
r"^I've added",
|
|
23
|
+
r"^I've updated",
|
|
24
|
+
r"^I've fixed",
|
|
25
|
+
r"^I've implemented",
|
|
26
|
+
r"^Done[.!]",
|
|
27
|
+
r"^Here's your",
|
|
28
|
+
r"^Here is your",
|
|
29
|
+
r"^All set[.!]",
|
|
30
|
+
r"^That's done",
|
|
31
|
+
r"^It's ready",
|
|
32
|
+
r"^The [\w\s]+ is ready",
|
|
33
|
+
r"^Your [\w\s]+ is ready",
|
|
34
34
|
]
|
|
35
35
|
|
|
36
|
+
# Long messages are summaries, not single-action completions
|
|
37
|
+
MAX_COMPLETION_LENGTH = 800
|
|
38
|
+
|
|
36
39
|
# File type -> contextual next steps
|
|
37
40
|
NEXT_STEPS = {
|
|
38
41
|
"html": [
|
|
@@ -123,7 +126,7 @@ def save_state(session_id, state):
|
|
|
123
126
|
|
|
124
127
|
|
|
125
128
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
126
|
-
from claudia_config import load_user_config
|
|
129
|
+
from claudia_config import load_user_config, dismiss_hint
|
|
127
130
|
|
|
128
131
|
|
|
129
132
|
def load_config():
|
|
@@ -147,8 +150,12 @@ def check(input_data, proactivity, experience):
|
|
|
147
150
|
if state["count"] >= 3:
|
|
148
151
|
return None
|
|
149
152
|
|
|
153
|
+
# Long messages are summaries/changelogs, not single-action completions
|
|
154
|
+
if len(message) > MAX_COMPLETION_LENGTH:
|
|
155
|
+
return None
|
|
156
|
+
|
|
150
157
|
is_completion = any(
|
|
151
|
-
re.search(pattern, message, re.IGNORECASE)
|
|
158
|
+
re.search(pattern, message, re.IGNORECASE | re.MULTILINE)
|
|
152
159
|
for pattern in COMPLETION_PATTERNS
|
|
153
160
|
)
|
|
154
161
|
|
|
@@ -184,9 +191,15 @@ def check(input_data, proactivity, experience):
|
|
|
184
191
|
suggestion_text = "Claudia: What's next? Here are some ideas:\n" + "\n".join(
|
|
185
192
|
f" - {step}" for step in formatted
|
|
186
193
|
)
|
|
194
|
+
system_msg = suggestion_text
|
|
195
|
+
context = suggestion_text
|
|
196
|
+
if state["count"] % 3 == 0:
|
|
197
|
+
user_hint, claude_hint = dismiss_hint("next-steps")
|
|
198
|
+
system_msg += "\n" + user_hint
|
|
199
|
+
context += "\n" + claude_hint
|
|
187
200
|
return {
|
|
188
|
-
"additionalContext":
|
|
189
|
-
"systemMessage": f"\033[38;5;160m{
|
|
201
|
+
"additionalContext": context,
|
|
202
|
+
"systemMessage": f"\033[38;5;160m{system_msg}\033[0m",
|
|
190
203
|
}
|
|
191
204
|
|
|
192
205
|
|
|
@@ -64,7 +64,7 @@ def save_state(session_id, state):
|
|
|
64
64
|
|
|
65
65
|
|
|
66
66
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
67
|
-
from claudia_config import load_user_config
|
|
67
|
+
from claudia_config import load_user_config, load_suppress_hooks, dismiss_hint
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
def load_config():
|
|
@@ -85,6 +85,10 @@ def main():
|
|
|
85
85
|
|
|
86
86
|
proactivity, experience = load_config()
|
|
87
87
|
|
|
88
|
+
# Bail if hook is suppressed
|
|
89
|
+
if "prompt-coach" in load_suppress_hooks():
|
|
90
|
+
sys.exit(0)
|
|
91
|
+
|
|
88
92
|
# Skip slash commands — user is using the system correctly
|
|
89
93
|
if prompt.startswith("/"):
|
|
90
94
|
sys.exit(0)
|
|
@@ -144,7 +148,7 @@ def main():
|
|
|
144
148
|
|
|
145
149
|
# Check for very short prompts (< 15 chars) that aren't slash commands
|
|
146
150
|
if not coaching_note and len(prompt) < 15 and is_beginner:
|
|
147
|
-
if not re.match(r'^(yes|no|ok|okay|sure|yeah|yep|nah|nope|thanks|ty|thx)\b', prompt, re.IGNORECASE):
|
|
151
|
+
if not re.match(r'^(yes|no|ok|okay|sure|yeah|yep|nah|nope|thanks|ty|thx|commit this|push it|push this|run tests|run it|do this|ship it|test it|build it|deploy it|lint it|format it|save it|merge it|revert it|undo that)\b', prompt, re.IGNORECASE):
|
|
148
152
|
coaching_note = (
|
|
149
153
|
"Claudia note: The user's prompt is quite short. They might benefit from "
|
|
150
154
|
"a gentle nudge to be more specific. Before responding, consider asking: "
|
|
@@ -174,7 +178,12 @@ def main():
|
|
|
174
178
|
save_state(session_id, state)
|
|
175
179
|
result = {"additionalContext": coaching_note}
|
|
176
180
|
if user_msg:
|
|
177
|
-
|
|
181
|
+
system_msg = user_msg
|
|
182
|
+
if state["count"] % 2 == 0:
|
|
183
|
+
user_hint, claude_hint = dismiss_hint("prompt-coach")
|
|
184
|
+
system_msg += "\n" + user_hint
|
|
185
|
+
result["additionalContext"] += "\n" + claude_hint
|
|
186
|
+
result["systemMessage"] = f"\033[38;5;160m{system_msg}\033[0m"
|
|
178
187
|
print(json.dumps(result))
|
|
179
188
|
|
|
180
189
|
sys.exit(0)
|
|
@@ -76,7 +76,7 @@ def save_state(session_id, state):
|
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
79
|
-
from claudia_config import load_user_config
|
|
79
|
+
from claudia_config import load_user_config, dismiss_hint
|
|
80
80
|
|
|
81
81
|
|
|
82
82
|
def load_config():
|
|
@@ -98,6 +98,10 @@ def check(input_data, proactivity, experience):
|
|
|
98
98
|
state = load_state(session_id)
|
|
99
99
|
shown_types = set(state.get("shown_types", []))
|
|
100
100
|
|
|
101
|
+
user_hint, claude_hint = dismiss_hint("run-suggest")
|
|
102
|
+
hint = "\n" + user_hint
|
|
103
|
+
ctx_hint = "\n" + claude_hint
|
|
104
|
+
|
|
101
105
|
# Check for package.json mentions
|
|
102
106
|
if "package.json" not in shown_types and re.search(PACKAGE_JSON_PATTERN, message, re.IGNORECASE):
|
|
103
107
|
shown_types.add("package.json")
|
|
@@ -105,7 +109,7 @@ def check(input_data, proactivity, experience):
|
|
|
105
109
|
save_state(session_id, state)
|
|
106
110
|
suggestion = RUN_SUGGESTIONS["package.json"][1]
|
|
107
111
|
msg = f"Claudia: {suggestion}"
|
|
108
|
-
return {"additionalContext": msg, "systemMessage": f"\033[38;5;160m{msg}\033[0m"}
|
|
112
|
+
return {"additionalContext": msg + ctx_hint, "systemMessage": f"\033[38;5;160m{msg}{hint}\033[0m"}
|
|
109
113
|
|
|
110
114
|
# Check for file creation patterns
|
|
111
115
|
for pattern in FILE_PATTERNS:
|
|
@@ -119,7 +123,7 @@ def check(input_data, proactivity, experience):
|
|
|
119
123
|
save_state(session_id, state)
|
|
120
124
|
suggestion = RUN_SUGGESTIONS[ext][1].format(filename=filename)
|
|
121
125
|
msg = f"Claudia: {suggestion}"
|
|
122
|
-
return {"additionalContext": msg, "systemMessage": f"\033[38;5;160m{msg}\033[0m"}
|
|
126
|
+
return {"additionalContext": msg + ctx_hint, "systemMessage": f"\033[38;5;160m{msg}{hint}\033[0m"}
|
|
123
127
|
|
|
124
128
|
return None
|
|
125
129
|
|
|
@@ -154,7 +154,7 @@ def save_state(session_id, state):
|
|
|
154
154
|
|
|
155
155
|
|
|
156
156
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
157
|
-
from claudia_config import load_user_config, load_project_context
|
|
157
|
+
from claudia_config import load_user_config, load_project_context, load_suppress_hooks
|
|
158
158
|
|
|
159
159
|
|
|
160
160
|
def load_config():
|
|
@@ -173,6 +173,10 @@ def main():
|
|
|
173
173
|
proactivity, experience = load_config()
|
|
174
174
|
is_beginner = experience == "beginner"
|
|
175
175
|
|
|
176
|
+
# Bail if hook is suppressed
|
|
177
|
+
if "session-tips" in load_suppress_hooks():
|
|
178
|
+
sys.exit(0)
|
|
179
|
+
|
|
176
180
|
# Low proactivity: skip tips, but still show greeting on startup
|
|
177
181
|
if proactivity == "low" and source != "startup":
|
|
178
182
|
sys.exit(0)
|
|
@@ -12,7 +12,7 @@ import sys
|
|
|
12
12
|
import time
|
|
13
13
|
|
|
14
14
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
15
|
-
from claudia_config import load_user_config
|
|
15
|
+
from claudia_config import load_user_config, load_suppress_topics, load_suppress_hooks
|
|
16
16
|
|
|
17
17
|
# Import check() from each Stop hook module
|
|
18
18
|
import importlib
|
|
@@ -56,9 +56,22 @@ def main():
|
|
|
56
56
|
|
|
57
57
|
session_id = input_data.get("session_id", "default")
|
|
58
58
|
proactivity, experience = load_user_config()
|
|
59
|
+
input_data["suppress_topics"] = load_suppress_topics()
|
|
60
|
+
suppress_hooks = load_suppress_hooks()
|
|
61
|
+
|
|
62
|
+
# Map module names to hook names for suppress check
|
|
63
|
+
MODULE_TO_HOOK = {
|
|
64
|
+
"claudia-milestones": "milestones",
|
|
65
|
+
"claudia-run-suggest": "run-suggest",
|
|
66
|
+
"claudia-next-steps": "next-steps",
|
|
67
|
+
"claudia-teach": "teach",
|
|
68
|
+
}
|
|
59
69
|
|
|
60
70
|
# Try each hook in order; first with output wins
|
|
61
71
|
for module_name in HOOK_MODULES:
|
|
72
|
+
hook_name = MODULE_TO_HOOK.get(module_name, module_name)
|
|
73
|
+
if hook_name in suppress_hooks:
|
|
74
|
+
continue
|
|
62
75
|
try:
|
|
63
76
|
mod = _import_hook(module_name)
|
|
64
77
|
result = mod.check(input_data, proactivity, experience)
|
|
@@ -237,7 +237,7 @@ def save_state(session_id, state):
|
|
|
237
237
|
|
|
238
238
|
|
|
239
239
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
240
|
-
from claudia_config import load_user_config
|
|
240
|
+
from claudia_config import load_user_config, dismiss_hint
|
|
241
241
|
|
|
242
242
|
|
|
243
243
|
def load_config():
|
|
@@ -265,16 +265,25 @@ def check(input_data, proactivity, experience):
|
|
|
265
265
|
revealed_commands = set(state["revealed_commands"])
|
|
266
266
|
tips = []
|
|
267
267
|
|
|
268
|
+
# Build suppressed topics set (case-insensitive)
|
|
269
|
+
suppress_topics = input_data.get("suppress_topics", [])
|
|
270
|
+
suppressed = {t.lower() for t in suppress_topics if isinstance(t, str)}
|
|
271
|
+
|
|
268
272
|
# Scan for technology keywords
|
|
269
273
|
for category, keywords in KEYWORDS.items():
|
|
274
|
+
if category.lower() in suppressed:
|
|
275
|
+
continue
|
|
270
276
|
for keyword, description in keywords.items():
|
|
277
|
+
if keyword.lower() in suppressed:
|
|
278
|
+
continue
|
|
271
279
|
pattern = r'\b' + re.escape(keyword) + r'\b'
|
|
272
280
|
if re.search(pattern, message, re.IGNORECASE):
|
|
273
281
|
if keyword.lower() not in shown_keywords:
|
|
274
282
|
shown_keywords.add(keyword.lower())
|
|
275
283
|
tips.append(
|
|
276
284
|
f"I noticed we're talking about {keyword} ({description}). "
|
|
277
|
-
f"Want me to explain more? Just say `/claudia:explain {keyword.lower()}
|
|
285
|
+
f"Want me to explain more? Just say `/claudia:explain {keyword.lower()}`\n"
|
|
286
|
+
f"(To stop tips about {keyword}, add \"{keyword}\" to \"suppress_topics\" in ~/.claude/claudia.json)"
|
|
278
287
|
)
|
|
279
288
|
break
|
|
280
289
|
if tips:
|
|
@@ -312,8 +321,14 @@ def check(input_data, proactivity, experience):
|
|
|
312
321
|
state["revealed_commands"] = list(revealed_commands)
|
|
313
322
|
save_state(session_id, state)
|
|
314
323
|
tip_text = "\n".join(f"Claudia: {tip}" for tip in tips)
|
|
315
|
-
|
|
316
|
-
|
|
324
|
+
system_text = tip_text
|
|
325
|
+
context = tip_text
|
|
326
|
+
if len(shown_keywords) % 3 == 0:
|
|
327
|
+
user_hint, claude_hint = dismiss_hint("teach")
|
|
328
|
+
system_text += "\n" + user_hint
|
|
329
|
+
context += "\n" + claude_hint
|
|
330
|
+
colored = "\n".join(f"\033[38;5;160m{line}\033[0m" for line in system_text.split("\n"))
|
|
331
|
+
return {"additionalContext": context, "systemMessage": colored}
|
|
317
332
|
|
|
318
333
|
if shown_keywords != set(state["shown_keywords"]) or revealed_commands != set(state["revealed_commands"]):
|
|
319
334
|
state["shown_keywords"] = list(shown_keywords)
|
|
@@ -47,8 +47,57 @@ def resolve_project():
|
|
|
47
47
|
|
|
48
48
|
# --- User Config ---
|
|
49
49
|
|
|
50
|
+
def load_suppress_topics():
|
|
51
|
+
"""Load suppress_topics list from ~/.claude/claudia.json.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
list of topic strings to suppress (empty if not set or file missing).
|
|
55
|
+
"""
|
|
56
|
+
config_path = os.path.expanduser("~/.claude/claudia.json")
|
|
57
|
+
if os.path.exists(config_path):
|
|
58
|
+
try:
|
|
59
|
+
with open(config_path) as f:
|
|
60
|
+
data = json.load(f)
|
|
61
|
+
topics = data.get("suppress_topics", [])
|
|
62
|
+
if isinstance(topics, list):
|
|
63
|
+
return topics
|
|
64
|
+
except (json.JSONDecodeError, IOError):
|
|
65
|
+
pass
|
|
66
|
+
return []
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def load_suppress_hooks():
|
|
70
|
+
"""Load suppress_hooks list from ~/.claude/claudia.json.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
set of lowercase hook name strings to suppress (empty if not set or file missing).
|
|
74
|
+
"""
|
|
75
|
+
config_path = os.path.expanduser("~/.claude/claudia.json")
|
|
76
|
+
if os.path.exists(config_path):
|
|
77
|
+
try:
|
|
78
|
+
with open(config_path) as f:
|
|
79
|
+
data = json.load(f)
|
|
80
|
+
hooks = data.get("suppress_hooks", [])
|
|
81
|
+
if isinstance(hooks, list):
|
|
82
|
+
return {h.lower() for h in hooks if isinstance(h, str)}
|
|
83
|
+
except (json.JSONDecodeError, IOError):
|
|
84
|
+
pass
|
|
85
|
+
return set()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def dismiss_hint(hook_name):
|
|
89
|
+
"""Return a user-visible dismiss hint and Claude-visible instruction."""
|
|
90
|
+
user_hint = f'(Say "silence {hook_name}" to turn this off)'
|
|
91
|
+
claude_hint = (
|
|
92
|
+
f'If the user asks to silence or stop {hook_name} tips, '
|
|
93
|
+
f'add "{hook_name}" to the "suppress_hooks" list in ~/.claude/claudia.json '
|
|
94
|
+
f'(create the file/key if missing).'
|
|
95
|
+
)
|
|
96
|
+
return user_hint, claude_hint
|
|
97
|
+
|
|
98
|
+
|
|
50
99
|
def load_user_config():
|
|
51
|
-
"""Load proactivity from ~/.claude/claudia.json,
|
|
100
|
+
"""Load proactivity and experience from ~/.claude/claudia.json, fallback to context.
|
|
52
101
|
|
|
53
102
|
Returns:
|
|
54
103
|
(proactivity, experience) tuple with string values.
|
|
@@ -58,17 +107,22 @@ def load_user_config():
|
|
|
58
107
|
experience = "intermediate"
|
|
59
108
|
|
|
60
109
|
config_path = os.path.expanduser("~/.claude/claudia.json")
|
|
110
|
+
experience_set = False
|
|
61
111
|
if os.path.exists(config_path):
|
|
62
112
|
try:
|
|
63
113
|
with open(config_path) as f:
|
|
64
114
|
data = json.load(f)
|
|
65
115
|
proactivity = data.get("proactivity", proactivity)
|
|
116
|
+
if "experience" in data:
|
|
117
|
+
experience = data["experience"]
|
|
118
|
+
experience_set = True
|
|
66
119
|
except (json.JSONDecodeError, IOError):
|
|
67
120
|
pass
|
|
68
121
|
|
|
69
|
-
# Experience
|
|
70
|
-
|
|
71
|
-
|
|
122
|
+
# Experience falls back to project context if not set in claudia.json
|
|
123
|
+
if not experience_set:
|
|
124
|
+
ctx = load_project_context()
|
|
125
|
+
experience = ctx.get("experience", experience)
|
|
72
126
|
|
|
73
127
|
return proactivity, experience
|
|
74
128
|
|
package/package.json
CHANGED