brainlayer 1.0.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.
Files changed (53) hide show
  1. brainlayer/__init__.py +3 -0
  2. brainlayer/cli/__init__.py +1545 -0
  3. brainlayer/cli/wizard.py +132 -0
  4. brainlayer/cli_new.py +151 -0
  5. brainlayer/client.py +164 -0
  6. brainlayer/clustering.py +736 -0
  7. brainlayer/daemon.py +1105 -0
  8. brainlayer/dashboard/README.md +129 -0
  9. brainlayer/dashboard/__init__.py +5 -0
  10. brainlayer/dashboard/app.py +151 -0
  11. brainlayer/dashboard/search.py +229 -0
  12. brainlayer/dashboard/views.py +230 -0
  13. brainlayer/embeddings.py +131 -0
  14. brainlayer/engine.py +550 -0
  15. brainlayer/index_new.py +87 -0
  16. brainlayer/mcp/__init__.py +1558 -0
  17. brainlayer/migrate.py +205 -0
  18. brainlayer/paths.py +43 -0
  19. brainlayer/pipeline/__init__.py +47 -0
  20. brainlayer/pipeline/analyze_communication.py +508 -0
  21. brainlayer/pipeline/brain_graph.py +567 -0
  22. brainlayer/pipeline/chat_tags.py +63 -0
  23. brainlayer/pipeline/chunk.py +422 -0
  24. brainlayer/pipeline/classify.py +472 -0
  25. brainlayer/pipeline/cluster_sampling.py +73 -0
  26. brainlayer/pipeline/enrichment.py +810 -0
  27. brainlayer/pipeline/extract.py +66 -0
  28. brainlayer/pipeline/extract_claude_desktop.py +149 -0
  29. brainlayer/pipeline/extract_corrections.py +231 -0
  30. brainlayer/pipeline/extract_markdown.py +195 -0
  31. brainlayer/pipeline/extract_whatsapp.py +227 -0
  32. brainlayer/pipeline/git_overlay.py +301 -0
  33. brainlayer/pipeline/longitudinal_analyzer.py +568 -0
  34. brainlayer/pipeline/obsidian_export.py +455 -0
  35. brainlayer/pipeline/operation_grouping.py +486 -0
  36. brainlayer/pipeline/plan_linking.py +313 -0
  37. brainlayer/pipeline/sanitize.py +549 -0
  38. brainlayer/pipeline/semantic_style.py +574 -0
  39. brainlayer/pipeline/session_enrichment.py +472 -0
  40. brainlayer/pipeline/style_embed.py +67 -0
  41. brainlayer/pipeline/style_index.py +139 -0
  42. brainlayer/pipeline/temporal_chains.py +203 -0
  43. brainlayer/pipeline/time_batcher.py +248 -0
  44. brainlayer/pipeline/unified_timeline.py +569 -0
  45. brainlayer/storage.py +66 -0
  46. brainlayer/store.py +155 -0
  47. brainlayer/taxonomy.json +80 -0
  48. brainlayer/vector_store.py +1891 -0
  49. brainlayer-1.0.0.dist-info/METADATA +313 -0
  50. brainlayer-1.0.0.dist-info/RECORD +53 -0
  51. brainlayer-1.0.0.dist-info/WHEEL +4 -0
  52. brainlayer-1.0.0.dist-info/entry_points.txt +4 -0
  53. brainlayer-1.0.0.dist-info/licenses/LICENSE +190 -0
@@ -0,0 +1,508 @@
1
+ """Analyze communication patterns to generate personalized rules.
2
+
3
+ Extracts patterns from:
4
+ 1. User's writing style (from WhatsApp, Claude chats)
5
+ 2. Claude's response patterns that work well for the user
6
+ 3. Common clarifying questions and their answers
7
+ 4. Semantic topic-based style analysis (optional, requires ML dependencies)
8
+
9
+ Generates rules for desktop apps to help with text interactions.
10
+ """
11
+
12
+ import json
13
+ import re
14
+ from collections import Counter
15
+ from pathlib import Path
16
+ from typing import Any, Optional
17
+
18
+ # Optional semantic analysis (requires sentence-transformers, sklearn)
19
+ try:
20
+ from .semantic_style import SemanticStyleAnalysis, SemanticStyleAnalyzer
21
+
22
+ HAS_SEMANTIC = True
23
+ except ImportError:
24
+ HAS_SEMANTIC = False
25
+
26
+
27
+ class CommunicationAnalyzer:
28
+ """Analyze communication patterns and generate rules."""
29
+
30
+ def __init__(self):
31
+ self.user_messages = []
32
+ self.assistant_messages = []
33
+ self.clarifying_questions = []
34
+
35
+ def add_whatsapp_messages(self, messages: list[dict]):
36
+ """Add WhatsApp messages to analysis."""
37
+ for msg in messages:
38
+ if msg["is_from_me"]:
39
+ self.user_messages.append(
40
+ {
41
+ "text": msg["text"],
42
+ "source": "whatsapp",
43
+ "timestamp": msg.get("timestamp"),
44
+ }
45
+ )
46
+
47
+ def add_claude_conversations(self, conversations: list[dict]):
48
+ """Add Claude conversations to analysis."""
49
+ for conv in conversations:
50
+ for msg in conv.get("messages", []):
51
+ if msg.get("role") == "user":
52
+ self.user_messages.append(
53
+ {
54
+ "text": msg.get("content", ""),
55
+ "source": "claude",
56
+ "timestamp": msg.get("timestamp"),
57
+ }
58
+ )
59
+ elif msg.get("role") == "assistant":
60
+ self.assistant_messages.append(
61
+ {
62
+ "text": msg.get("content", ""),
63
+ "source": "claude",
64
+ "timestamp": msg.get("timestamp"),
65
+ }
66
+ )
67
+
68
+ # Detect clarifying questions
69
+ if self._is_clarifying_question(msg.get("content", "")):
70
+ self.clarifying_questions.append(msg.get("content", ""))
71
+
72
+ def _is_clarifying_question(self, text: str) -> bool:
73
+ """Detect if a message is a clarifying question."""
74
+ question_markers = [
75
+ "could you clarify",
76
+ "what do you mean",
77
+ "can you provide more details",
78
+ "would you like me to",
79
+ "should i",
80
+ "do you want",
81
+ "which",
82
+ "how would you like",
83
+ ]
84
+ text_lower = text.lower()
85
+ return any(marker in text_lower for marker in question_markers) and "?" in text
86
+
87
+ def analyze_writing_style(self) -> dict[str, Any]:
88
+ """Analyze user's writing style."""
89
+ if not self.user_messages:
90
+ return {}
91
+
92
+ texts = [msg["text"] for msg in self.user_messages]
93
+
94
+ # Length analysis
95
+ lengths = [len(text) for text in texts]
96
+ avg_length = sum(lengths) / len(lengths)
97
+
98
+ # Sentence structure
99
+ sentences_per_message = [text.count(".") + text.count("!") + text.count("?") for text in texts]
100
+ avg_sentences = sum(sentences_per_message) / len(sentences_per_message)
101
+
102
+ # Formality analysis - comprehensive markers
103
+ # English informal markers
104
+ informal_markers_en = [
105
+ "lol",
106
+ "haha",
107
+ "hahaha",
108
+ "btw",
109
+ "idk",
110
+ "tbh",
111
+ "ngl",
112
+ "omg",
113
+ "wtf",
114
+ "gonna",
115
+ "wanna",
116
+ "gotta",
117
+ "kinda",
118
+ "sorta",
119
+ "dunno",
120
+ "lemme",
121
+ "yeah",
122
+ "yep",
123
+ "nope",
124
+ "nah",
125
+ "yup",
126
+ "ok",
127
+ "okay",
128
+ "k",
129
+ "cool",
130
+ "awesome",
131
+ "dude",
132
+ "guys",
133
+ "hey",
134
+ "yo",
135
+ "sup",
136
+ "lmao",
137
+ "lmfao",
138
+ "rofl",
139
+ "brb",
140
+ "ttyl",
141
+ "imo",
142
+ "imho",
143
+ "fyi",
144
+ "thx",
145
+ "ty",
146
+ "np",
147
+ "pls",
148
+ "plz",
149
+ "rn",
150
+ "asap",
151
+ "afaik",
152
+ ]
153
+ # Hebrew informal markers
154
+ informal_markers_he = [
155
+ "חח",
156
+ "חחח",
157
+ "חחחח",
158
+ "לול",
159
+ "אוקי",
160
+ "יאללה",
161
+ "סבבה",
162
+ "וואלה",
163
+ "אחלה",
164
+ "יופי",
165
+ "בסדר",
166
+ "טוב",
167
+ "נו",
168
+ "מה",
169
+ "כן",
170
+ "לא",
171
+ "אז",
172
+ "רגע",
173
+ "שניה",
174
+ "בקיצור",
175
+ ]
176
+ informal_markers = informal_markers_en + informal_markers_he
177
+
178
+ # Count informal markers
179
+ informal_count = sum(1 for text in texts for marker in informal_markers if marker in text.lower())
180
+
181
+ # Also check for formal indicators (increase formality score)
182
+ formal_markers = [
183
+ "dear",
184
+ "sincerely",
185
+ "regards",
186
+ "respectfully",
187
+ "hereby",
188
+ "pursuant",
189
+ "kindly",
190
+ "please find",
191
+ "attached herewith",
192
+ "i am writing to",
193
+ "to whom it may concern",
194
+ ]
195
+ formal_count = sum(1 for text in texts for marker in formal_markers if marker in text.lower())
196
+
197
+ # Check for casual style indicators (count messages with casual traits)
198
+ casual_messages = 0
199
+ for text in texts:
200
+ is_casual = False
201
+ # Short messages are casual
202
+ if len(text) < 30:
203
+ is_casual = True
204
+ # Messages without proper capitalization
205
+ if text and text[0].islower():
206
+ is_casual = True
207
+ # Messages ending without punctuation
208
+ if text and text[-1] not in ".!?":
209
+ is_casual = True
210
+ # Contractions
211
+ if any(c in text.lower() for c in ["don't", "won't", "can't", "i'm", "it's", "that's", "what's"]):
212
+ is_casual = True
213
+ if is_casual:
214
+ casual_messages += 1
215
+
216
+ # Calculate ratios (all 0-1 range now, clamped for consistency)
217
+ informal_marker_ratio = min(1.0, informal_count / max(len(texts), 1))
218
+ casual_style_ratio = casual_messages / max(len(texts), 1)
219
+ formal_marker_ratio = min(1.0, formal_count / max(len(texts), 1))
220
+
221
+ # Formality score: 0 = very casual, 1 = very formal
222
+ # Start at 0.5, subtract for informal/casual indicators, add for formal
223
+ # Tuned weights: casual=0.15, informal=0.1 (lighter touch to avoid extremes)
224
+ raw_score = 0.5 - (casual_style_ratio * 0.15) - (informal_marker_ratio * 0.1) + (formal_marker_ratio * 0.3)
225
+ formality_score = max(0.1, min(0.9, raw_score)) # Clamp to 0.1-0.9 range
226
+
227
+ # Punctuation patterns
228
+ exclamation_rate = sum(text.count("!") for text in texts) / len(texts)
229
+ question_rate = sum(text.count("?") for text in texts) / len(texts)
230
+
231
+ # Emoji usage
232
+ emoji_pattern = re.compile(
233
+ "["
234
+ "\U0001f600-\U0001f64f" # emoticons
235
+ "\U0001f300-\U0001f5ff" # symbols & pictographs
236
+ "\U0001f680-\U0001f6ff" # transport & map symbols
237
+ "\U0001f1e0-\U0001f1ff" # flags
238
+ "\U00002702-\U000027b0"
239
+ "\U000024c2-\U0001f251"
240
+ "]+",
241
+ flags=re.UNICODE,
242
+ )
243
+ emoji_count = sum(len(emoji_pattern.findall(text)) for text in texts)
244
+ emoji_rate = emoji_count / len(texts)
245
+
246
+ # Common phrases
247
+ all_text = " ".join(texts).lower()
248
+ words = re.findall(r"\b\w+\b", all_text)
249
+ word_freq = Counter(words)
250
+ common_words = [word for word, count in word_freq.most_common(20) if len(word) > 3]
251
+
252
+ # Greeting patterns
253
+ greetings = ["hi", "hello", "hey", "good morning", "good afternoon", "good evening"]
254
+ greeting_usage = sum(1 for text in texts for greeting in greetings if text.lower().startswith(greeting)) / len(
255
+ texts
256
+ )
257
+
258
+ return {
259
+ "avg_message_length": avg_length,
260
+ "avg_sentences_per_message": avg_sentences,
261
+ "formality_score": formality_score, # 0 = very informal, 1 = very formal
262
+ "exclamation_rate": exclamation_rate,
263
+ "question_rate": question_rate,
264
+ "emoji_rate": emoji_rate,
265
+ "common_words": common_words,
266
+ "greeting_usage": greeting_usage,
267
+ "total_messages_analyzed": len(texts),
268
+ }
269
+
270
+ def analyze_claude_response_patterns(self) -> dict[str, Any]:
271
+ """Analyze Claude's response patterns that work well."""
272
+ if not self.assistant_messages:
273
+ return {}
274
+
275
+ texts = [msg["text"] for msg in self.assistant_messages]
276
+
277
+ # Response structure
278
+ uses_bullet_points = sum("•" in text or "- " in text for text in texts) / len(texts)
279
+ uses_code_blocks = sum("```" in text for text in texts) / len(texts)
280
+ uses_numbered_lists = sum(bool(re.search(r"\n\d+\.", text)) for text in texts) / len(texts)
281
+
282
+ # Tone analysis
283
+ encouraging_phrases = ["great", "excellent", "perfect", "nice work", "well done"]
284
+ encouraging_rate = sum(1 for text in texts for phrase in encouraging_phrases if phrase in text.lower()) / len(
285
+ texts
286
+ )
287
+
288
+ # Explanation style
289
+ uses_examples = sum("for example" in text.lower() or "e.g." in text.lower() for text in texts) / len(texts)
290
+ uses_analogies = sum("like" in text.lower() or "similar to" in text.lower() for text in texts) / len(texts)
291
+
292
+ return {
293
+ "uses_bullet_points_rate": uses_bullet_points,
294
+ "uses_code_blocks_rate": uses_code_blocks,
295
+ "uses_numbered_lists_rate": uses_numbered_lists,
296
+ "encouraging_rate": encouraging_rate,
297
+ "uses_examples_rate": uses_examples,
298
+ "uses_analogies_rate": uses_analogies,
299
+ "total_responses_analyzed": len(texts),
300
+ }
301
+
302
+ def extract_common_clarifications(self) -> list[str]:
303
+ """Extract common clarifying questions Claude asks."""
304
+ if not self.clarifying_questions:
305
+ return []
306
+
307
+ # Group similar questions
308
+ question_patterns = []
309
+ for q in self.clarifying_questions[:20]: # Top 20
310
+ # Extract the core question
311
+ core = re.sub(r"^(could you|can you|would you|should i)\s+", "", q.lower())
312
+ question_patterns.append(core)
313
+
314
+ return question_patterns
315
+
316
+ def generate_rules(self, output_path: Path):
317
+ """Generate Cursor rules based on analysis."""
318
+ writing_style = self.analyze_writing_style()
319
+ response_patterns = self.analyze_claude_response_patterns()
320
+ clarifications = self.extract_common_clarifications()
321
+
322
+ rules = self._format_rules(writing_style, response_patterns, clarifications)
323
+
324
+ output_path.parent.mkdir(parents=True, exist_ok=True)
325
+ with open(output_path, "w") as f:
326
+ f.write(rules)
327
+
328
+ return rules
329
+
330
+ def _format_rules(self, writing_style: dict, response_patterns: dict, clarifications: list[str]) -> str:
331
+ """Format analysis into Cursor rules."""
332
+
333
+ # Determine tone
334
+ if writing_style.get("formality_score", 0.5) > 0.7:
335
+ tone = "professional and formal"
336
+ elif writing_style.get("formality_score", 0.5) < 0.3:
337
+ tone = "casual and conversational"
338
+ else:
339
+ tone = "balanced between casual and professional"
340
+
341
+ # Determine verbosity
342
+ avg_length = writing_style.get("avg_message_length", 100)
343
+ if avg_length < 50:
344
+ verbosity = "concise and to-the-point"
345
+ elif avg_length > 200:
346
+ verbosity = "detailed and thorough"
347
+ else:
348
+ verbosity = "moderately detailed"
349
+
350
+ # Emoji preference
351
+ emoji_rate = writing_style.get("emoji_rate", 0)
352
+ emoji_pref = "Use emojis occasionally" if emoji_rate > 0.5 else "Avoid emojis unless requested"
353
+
354
+ # Response structure preference
355
+ structure_prefs = []
356
+ if response_patterns.get("uses_bullet_points_rate", 0) > 0.5:
357
+ structure_prefs.append("Use bullet points for lists")
358
+ if response_patterns.get("uses_numbered_lists_rate", 0) > 0.5:
359
+ structure_prefs.append("Use numbered lists for sequential steps")
360
+ if response_patterns.get("uses_examples_rate", 0) > 0.5:
361
+ structure_prefs.append("Provide concrete examples")
362
+
363
+ rules_content = f"""# Communication Style Rules
364
+ # Auto-generated from analysis of {writing_style.get("total_messages_analyzed", 0)} messages
365
+
366
+ ## User's Writing Style
367
+
368
+ **Tone**: {tone}
369
+ **Verbosity**: {verbosity}
370
+ **Average message length**: {writing_style.get("avg_message_length", 0):.0f} characters
371
+ **Formality score**: {writing_style.get("formality_score", 0):.2f} (0=informal, 1=formal)
372
+
373
+ ### Preferences
374
+
375
+ - {emoji_pref}
376
+ - Exclamation usage: {writing_style.get("exclamation_rate", 0):.2f} per message
377
+ - Question usage: {writing_style.get("question_rate", 0):.2f} per message
378
+
379
+ ## Response Style Guidelines
380
+
381
+ When helping this user with text interactions:
382
+
383
+ 1. **Match their tone**: Write in a {tone} style
384
+ 2. **Match their verbosity**: Keep responses {verbosity}
385
+ 3. **Structure**:
386
+ {chr(10).join(f" - {pref}" for pref in structure_prefs) if structure_prefs else " - Use clear, simple structure"}
387
+
388
+ ## Common Clarifying Questions
389
+
390
+ When the user's request is ambiguous, consider asking:
391
+
392
+ {chr(10).join(f"- {q}" for q in clarifications[:10]) if clarifications else "- Ask for specific details about their goal"}
393
+
394
+ ## Desktop App Integration
395
+
396
+ When helping draft messages or responses:
397
+
398
+ 1. **Analyze the context**: What is the user trying to communicate?
399
+ 2. **Match their style**: Use the tone and verbosity patterns above
400
+ 3. **Ask clarifying questions**: If unclear, ask specific questions before drafting
401
+ 4. **Provide options**: Offer 2-3 variations (formal, casual, concise)
402
+
403
+ ## Example Phrases
404
+
405
+ Common words/phrases this user uses:
406
+ {", ".join(writing_style.get("common_words", [])[:15])}
407
+
408
+ Use these naturally when appropriate to match their voice.
409
+
410
+ ---
411
+
412
+ *Generated: {Path.cwd()}*
413
+ *Analysis based on WhatsApp and Claude chat history*
414
+ """
415
+
416
+ return rules_content
417
+
418
+ def export_analysis(self, output_path: Path):
419
+ """Export full analysis as JSON."""
420
+ analysis = {
421
+ "writing_style": self.analyze_writing_style(),
422
+ "response_patterns": self.analyze_claude_response_patterns(),
423
+ "clarifying_questions": self.extract_common_clarifications(),
424
+ "sample_user_messages": [msg["text"] for msg in self.user_messages[:20]],
425
+ "sample_assistant_messages": [msg["text"] for msg in self.assistant_messages[:20]],
426
+ }
427
+
428
+ output_path.parent.mkdir(parents=True, exist_ok=True)
429
+ with open(output_path, "w") as f:
430
+ json.dump(analysis, f, indent=2)
431
+
432
+ return analysis
433
+
434
+ def analyze_semantic_style(
435
+ self,
436
+ min_cluster_size: int = 10,
437
+ ) -> Optional["SemanticStyleAnalysis"]:
438
+ """Run semantic style analysis on user messages.
439
+
440
+ Clusters messages by topic and analyzes style patterns per topic.
441
+ Requires sentence-transformers and scikit-learn.
442
+
443
+ Args:
444
+ min_cluster_size: Minimum messages per topic cluster
445
+
446
+ Returns:
447
+ SemanticStyleAnalysis or None if dependencies unavailable
448
+ """
449
+ if not HAS_SEMANTIC:
450
+ print("[SemanticStyle] Dependencies not available. Install: pip install sentence-transformers scikit-learn")
451
+ return None
452
+
453
+ if not self.user_messages:
454
+ print("[SemanticStyle] No user messages to analyze")
455
+ return None
456
+
457
+ texts = [msg["text"] for msg in self.user_messages if msg.get("text")]
458
+ if len(texts) < min_cluster_size * 2:
459
+ print(f"[SemanticStyle] Need at least {min_cluster_size * 2} messages, have {len(texts)}")
460
+ return None
461
+
462
+ analyzer = SemanticStyleAnalyzer()
463
+ return analyzer.analyze(texts, min_cluster_size=min_cluster_size)
464
+
465
+ def generate_semantic_rules(
466
+ self,
467
+ output_dir: Path,
468
+ min_cluster_size: int = 10,
469
+ ) -> Optional[Path]:
470
+ """Generate semantic style rules and save to directory.
471
+
472
+ Args:
473
+ output_dir: Directory to save semantic-style-rules.md and JSON
474
+ min_cluster_size: Minimum messages per topic cluster
475
+
476
+ Returns:
477
+ Path to markdown file or None if analysis failed
478
+ """
479
+ analysis = self.analyze_semantic_style(min_cluster_size=min_cluster_size)
480
+ if analysis is None:
481
+ return None
482
+
483
+ output_dir.mkdir(parents=True, exist_ok=True)
484
+
485
+ # Save markdown rules
486
+ rules_path = output_dir / "semantic-style-rules.md"
487
+ rules_path.write_text(analysis.style_rules_markdown)
488
+
489
+ # Save JSON data
490
+ data = {
491
+ "topics": {
492
+ name: {
493
+ "message_count": len(cluster.messages),
494
+ "avg_length": cluster.avg_length,
495
+ "formality": cluster.formality,
496
+ "emoji_rate": cluster.emoji_rate,
497
+ "language_mix": cluster.language_mix,
498
+ "common_phrases": cluster.common_phrases,
499
+ }
500
+ for name, cluster in analysis.topic_clusters.items()
501
+ },
502
+ "insights": analysis.cross_topic_insights,
503
+ }
504
+ json_path = output_dir / "semantic-style-data.json"
505
+ json_path.write_text(json.dumps(data, indent=2, ensure_ascii=False))
506
+
507
+ print(f"[SemanticStyle] Saved rules to {rules_path}")
508
+ return rules_path