gitflow-analytics 1.0.1__py3-none-any.whl → 1.3.6__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 (119) hide show
  1. gitflow_analytics/__init__.py +11 -11
  2. gitflow_analytics/_version.py +2 -2
  3. gitflow_analytics/classification/__init__.py +31 -0
  4. gitflow_analytics/classification/batch_classifier.py +752 -0
  5. gitflow_analytics/classification/classifier.py +464 -0
  6. gitflow_analytics/classification/feature_extractor.py +725 -0
  7. gitflow_analytics/classification/linguist_analyzer.py +574 -0
  8. gitflow_analytics/classification/model.py +455 -0
  9. gitflow_analytics/cli.py +4490 -378
  10. gitflow_analytics/cli_rich.py +503 -0
  11. gitflow_analytics/config/__init__.py +43 -0
  12. gitflow_analytics/config/errors.py +261 -0
  13. gitflow_analytics/config/loader.py +904 -0
  14. gitflow_analytics/config/profiles.py +264 -0
  15. gitflow_analytics/config/repository.py +124 -0
  16. gitflow_analytics/config/schema.py +441 -0
  17. gitflow_analytics/config/validator.py +154 -0
  18. gitflow_analytics/config.py +44 -398
  19. gitflow_analytics/core/analyzer.py +1320 -172
  20. gitflow_analytics/core/branch_mapper.py +132 -132
  21. gitflow_analytics/core/cache.py +1554 -175
  22. gitflow_analytics/core/data_fetcher.py +1193 -0
  23. gitflow_analytics/core/identity.py +571 -185
  24. gitflow_analytics/core/metrics_storage.py +526 -0
  25. gitflow_analytics/core/progress.py +372 -0
  26. gitflow_analytics/core/schema_version.py +269 -0
  27. gitflow_analytics/extractors/base.py +13 -11
  28. gitflow_analytics/extractors/ml_tickets.py +1100 -0
  29. gitflow_analytics/extractors/story_points.py +77 -59
  30. gitflow_analytics/extractors/tickets.py +841 -89
  31. gitflow_analytics/identity_llm/__init__.py +6 -0
  32. gitflow_analytics/identity_llm/analysis_pass.py +231 -0
  33. gitflow_analytics/identity_llm/analyzer.py +464 -0
  34. gitflow_analytics/identity_llm/models.py +76 -0
  35. gitflow_analytics/integrations/github_integration.py +258 -87
  36. gitflow_analytics/integrations/jira_integration.py +572 -123
  37. gitflow_analytics/integrations/orchestrator.py +206 -82
  38. gitflow_analytics/metrics/activity_scoring.py +322 -0
  39. gitflow_analytics/metrics/branch_health.py +470 -0
  40. gitflow_analytics/metrics/dora.py +542 -179
  41. gitflow_analytics/models/database.py +986 -59
  42. gitflow_analytics/pm_framework/__init__.py +115 -0
  43. gitflow_analytics/pm_framework/adapters/__init__.py +50 -0
  44. gitflow_analytics/pm_framework/adapters/jira_adapter.py +1845 -0
  45. gitflow_analytics/pm_framework/base.py +406 -0
  46. gitflow_analytics/pm_framework/models.py +211 -0
  47. gitflow_analytics/pm_framework/orchestrator.py +652 -0
  48. gitflow_analytics/pm_framework/registry.py +333 -0
  49. gitflow_analytics/qualitative/__init__.py +29 -0
  50. gitflow_analytics/qualitative/chatgpt_analyzer.py +259 -0
  51. gitflow_analytics/qualitative/classifiers/__init__.py +13 -0
  52. gitflow_analytics/qualitative/classifiers/change_type.py +742 -0
  53. gitflow_analytics/qualitative/classifiers/domain_classifier.py +506 -0
  54. gitflow_analytics/qualitative/classifiers/intent_analyzer.py +535 -0
  55. gitflow_analytics/qualitative/classifiers/llm/__init__.py +35 -0
  56. gitflow_analytics/qualitative/classifiers/llm/base.py +193 -0
  57. gitflow_analytics/qualitative/classifiers/llm/batch_processor.py +383 -0
  58. gitflow_analytics/qualitative/classifiers/llm/cache.py +479 -0
  59. gitflow_analytics/qualitative/classifiers/llm/cost_tracker.py +435 -0
  60. gitflow_analytics/qualitative/classifiers/llm/openai_client.py +403 -0
  61. gitflow_analytics/qualitative/classifiers/llm/prompts.py +373 -0
  62. gitflow_analytics/qualitative/classifiers/llm/response_parser.py +287 -0
  63. gitflow_analytics/qualitative/classifiers/llm_commit_classifier.py +607 -0
  64. gitflow_analytics/qualitative/classifiers/risk_analyzer.py +438 -0
  65. gitflow_analytics/qualitative/core/__init__.py +13 -0
  66. gitflow_analytics/qualitative/core/llm_fallback.py +657 -0
  67. gitflow_analytics/qualitative/core/nlp_engine.py +382 -0
  68. gitflow_analytics/qualitative/core/pattern_cache.py +479 -0
  69. gitflow_analytics/qualitative/core/processor.py +673 -0
  70. gitflow_analytics/qualitative/enhanced_analyzer.py +2236 -0
  71. gitflow_analytics/qualitative/example_enhanced_usage.py +420 -0
  72. gitflow_analytics/qualitative/models/__init__.py +25 -0
  73. gitflow_analytics/qualitative/models/schemas.py +306 -0
  74. gitflow_analytics/qualitative/utils/__init__.py +13 -0
  75. gitflow_analytics/qualitative/utils/batch_processor.py +339 -0
  76. gitflow_analytics/qualitative/utils/cost_tracker.py +345 -0
  77. gitflow_analytics/qualitative/utils/metrics.py +361 -0
  78. gitflow_analytics/qualitative/utils/text_processing.py +285 -0
  79. gitflow_analytics/reports/__init__.py +100 -0
  80. gitflow_analytics/reports/analytics_writer.py +550 -18
  81. gitflow_analytics/reports/base.py +648 -0
  82. gitflow_analytics/reports/branch_health_writer.py +322 -0
  83. gitflow_analytics/reports/classification_writer.py +924 -0
  84. gitflow_analytics/reports/cli_integration.py +427 -0
  85. gitflow_analytics/reports/csv_writer.py +1700 -216
  86. gitflow_analytics/reports/data_models.py +504 -0
  87. gitflow_analytics/reports/database_report_generator.py +427 -0
  88. gitflow_analytics/reports/example_usage.py +344 -0
  89. gitflow_analytics/reports/factory.py +499 -0
  90. gitflow_analytics/reports/formatters.py +698 -0
  91. gitflow_analytics/reports/html_generator.py +1116 -0
  92. gitflow_analytics/reports/interfaces.py +489 -0
  93. gitflow_analytics/reports/json_exporter.py +2770 -0
  94. gitflow_analytics/reports/narrative_writer.py +2289 -158
  95. gitflow_analytics/reports/story_point_correlation.py +1144 -0
  96. gitflow_analytics/reports/weekly_trends_writer.py +389 -0
  97. gitflow_analytics/training/__init__.py +5 -0
  98. gitflow_analytics/training/model_loader.py +377 -0
  99. gitflow_analytics/training/pipeline.py +550 -0
  100. gitflow_analytics/tui/__init__.py +5 -0
  101. gitflow_analytics/tui/app.py +724 -0
  102. gitflow_analytics/tui/screens/__init__.py +8 -0
  103. gitflow_analytics/tui/screens/analysis_progress_screen.py +496 -0
  104. gitflow_analytics/tui/screens/configuration_screen.py +523 -0
  105. gitflow_analytics/tui/screens/loading_screen.py +348 -0
  106. gitflow_analytics/tui/screens/main_screen.py +321 -0
  107. gitflow_analytics/tui/screens/results_screen.py +722 -0
  108. gitflow_analytics/tui/widgets/__init__.py +7 -0
  109. gitflow_analytics/tui/widgets/data_table.py +255 -0
  110. gitflow_analytics/tui/widgets/export_modal.py +301 -0
  111. gitflow_analytics/tui/widgets/progress_widget.py +187 -0
  112. gitflow_analytics-1.3.6.dist-info/METADATA +1015 -0
  113. gitflow_analytics-1.3.6.dist-info/RECORD +122 -0
  114. gitflow_analytics-1.0.1.dist-info/METADATA +0 -463
  115. gitflow_analytics-1.0.1.dist-info/RECORD +0 -31
  116. {gitflow_analytics-1.0.1.dist-info → gitflow_analytics-1.3.6.dist-info}/WHEEL +0 -0
  117. {gitflow_analytics-1.0.1.dist-info → gitflow_analytics-1.3.6.dist-info}/entry_points.txt +0 -0
  118. {gitflow_analytics-1.0.1.dist-info → gitflow_analytics-1.3.6.dist-info}/licenses/LICENSE +0 -0
  119. {gitflow_analytics-1.0.1.dist-info → gitflow_analytics-1.3.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,535 @@
1
+ """Intent analyzer for extracting developer intent and urgency from commits."""
2
+
3
+ import importlib.util
4
+ import logging
5
+ import re
6
+ from collections import defaultdict
7
+ from typing import Any
8
+
9
+ from ..models.schemas import IntentConfig
10
+
11
+ # Check if spacy is available without importing it
12
+ SPACY_AVAILABLE = importlib.util.find_spec("spacy") is not None
13
+
14
+ if SPACY_AVAILABLE:
15
+ from spacy.tokens import Doc
16
+ else:
17
+ Doc = Any
18
+
19
+
20
+ class IntentAnalyzer:
21
+ """Analyze commit messages to extract developer intent and urgency signals.
22
+
23
+ This analyzer identifies:
24
+ - Urgency level (critical, important, routine)
25
+ - Intent confidence (how clear the intent is)
26
+ - Emotional tone (frustrated, confident, uncertain)
27
+ - Planning signals (TODO, FIXME, temporary fixes)
28
+ - Collaboration signals (pair programming, code review)
29
+ """
30
+
31
+ def __init__(self, config: IntentConfig):
32
+ """Initialize intent analyzer.
33
+
34
+ Args:
35
+ config: Configuration for intent analysis
36
+ """
37
+ self.config = config
38
+ self.logger = logging.getLogger(__name__)
39
+
40
+ # Urgency keyword patterns from config
41
+ self.urgency_keywords = config.urgency_keywords
42
+
43
+ # Confidence indicators
44
+ self.confidence_indicators = {
45
+ "high_confidence": {
46
+ "definitely",
47
+ "clearly",
48
+ "obviously",
49
+ "certainly",
50
+ "confirmed",
51
+ "verified",
52
+ "tested",
53
+ "working",
54
+ "complete",
55
+ "finished",
56
+ "implement",
57
+ "solution",
58
+ "resolve",
59
+ },
60
+ "low_confidence": {
61
+ "maybe",
62
+ "perhaps",
63
+ "possibly",
64
+ "might",
65
+ "could",
66
+ "should",
67
+ "try",
68
+ "attempt",
69
+ "experiment",
70
+ "test",
71
+ "temporary",
72
+ "quick",
73
+ "hack",
74
+ "workaround",
75
+ "temp",
76
+ },
77
+ "uncertain": {
78
+ "not sure",
79
+ "unclear",
80
+ "confusing",
81
+ "weird",
82
+ "strange",
83
+ "unexpected",
84
+ "unsure",
85
+ "investigation",
86
+ "debug",
87
+ "investigate",
88
+ },
89
+ }
90
+
91
+ # Emotional tone indicators
92
+ self.tone_indicators = {
93
+ "frustrated": {
94
+ "annoying",
95
+ "frustrating",
96
+ "stupid",
97
+ "broken",
98
+ "terrible",
99
+ "awful",
100
+ "hate",
101
+ "annoyed",
102
+ "ugh",
103
+ "argh",
104
+ "damn",
105
+ "wtf",
106
+ },
107
+ "confident": {
108
+ "great",
109
+ "excellent",
110
+ "perfect",
111
+ "awesome",
112
+ "clean",
113
+ "elegant",
114
+ "nice",
115
+ "good",
116
+ "better",
117
+ "improved",
118
+ "optimized",
119
+ },
120
+ "cautious": {
121
+ "careful",
122
+ "cautious",
123
+ "gentle",
124
+ "safe",
125
+ "conservative",
126
+ "minimal",
127
+ "small",
128
+ "incremental",
129
+ "gradual",
130
+ },
131
+ }
132
+
133
+ # Planning and TODO indicators
134
+ self.planning_indicators = {
135
+ "todo": {
136
+ "todo",
137
+ "fixme",
138
+ "hack",
139
+ "temporary",
140
+ "temp",
141
+ "later",
142
+ "placeholder",
143
+ "stub",
144
+ "incomplete",
145
+ "wip",
146
+ },
147
+ "future_work": {
148
+ "future",
149
+ "later",
150
+ "eventually",
151
+ "someday",
152
+ "next",
153
+ "upcoming",
154
+ "planned",
155
+ "roadmap",
156
+ },
157
+ "immediate": {
158
+ "now",
159
+ "immediate",
160
+ "urgent",
161
+ "asap",
162
+ "quickly",
163
+ "fast",
164
+ "emergency",
165
+ "critical",
166
+ "hotfix",
167
+ },
168
+ }
169
+
170
+ # Collaboration indicators
171
+ self.collaboration_indicators = {
172
+ "pair_programming": {
173
+ "pair",
174
+ "pairing",
175
+ "together",
176
+ "with",
177
+ "collaborative",
178
+ "co-authored",
179
+ "mob",
180
+ "mobbing",
181
+ },
182
+ "code_review": {
183
+ "review",
184
+ "feedback",
185
+ "suggestion",
186
+ "requested",
187
+ "comment",
188
+ "pr",
189
+ "pull request",
190
+ "merge request",
191
+ },
192
+ "help_seeking": {
193
+ "help",
194
+ "assistance",
195
+ "advice",
196
+ "guidance",
197
+ "input",
198
+ "thoughts",
199
+ "opinions",
200
+ "feedback",
201
+ },
202
+ }
203
+
204
+ # Technical complexity indicators
205
+ self.complexity_indicators = {
206
+ "simple": {
207
+ "simple",
208
+ "easy",
209
+ "quick",
210
+ "minor",
211
+ "small",
212
+ "tiny",
213
+ "straightforward",
214
+ "basic",
215
+ },
216
+ "complex": {
217
+ "complex",
218
+ "complicated",
219
+ "difficult",
220
+ "challenging",
221
+ "major",
222
+ "significant",
223
+ "substantial",
224
+ "extensive",
225
+ },
226
+ "refactoring": {
227
+ "refactor",
228
+ "restructure",
229
+ "reorganize",
230
+ "cleanup",
231
+ "simplify",
232
+ "optimize",
233
+ "improve",
234
+ },
235
+ }
236
+
237
+ def analyze(self, message: str, doc: Doc) -> dict[str, Any]:
238
+ """Analyze commit message for intent signals.
239
+
240
+ Args:
241
+ message: Commit message
242
+ doc: spaCy processed document (may be None)
243
+
244
+ Returns:
245
+ Dictionary with intent analysis results
246
+ """
247
+ if not message:
248
+ return {
249
+ "urgency": "routine",
250
+ "confidence": 0.0,
251
+ "tone": "neutral",
252
+ "planning_stage": "implementation",
253
+ "collaboration_signals": [],
254
+ "complexity": "moderate",
255
+ "signals": [],
256
+ }
257
+
258
+ message_lower = message.lower()
259
+
260
+ # Extract all signals
261
+ urgency = self._analyze_urgency(message_lower)
262
+ confidence_info = self._analyze_confidence(message_lower, doc)
263
+ tone = self._analyze_tone(message_lower)
264
+ planning = self._analyze_planning_stage(message_lower)
265
+ collaboration = self._analyze_collaboration(message_lower)
266
+ complexity = self._analyze_complexity(message_lower)
267
+
268
+ # Collect all detected signals
269
+ all_signals = []
270
+ all_signals.extend(urgency.get("signals", []))
271
+ all_signals.extend(confidence_info.get("signals", []))
272
+ all_signals.extend(tone.get("signals", []))
273
+ all_signals.extend(planning.get("signals", []))
274
+ all_signals.extend(collaboration.get("signals", []))
275
+ all_signals.extend(complexity.get("signals", []))
276
+
277
+ return {
278
+ "urgency": urgency["level"],
279
+ "confidence": confidence_info["score"],
280
+ "tone": tone["dominant_tone"],
281
+ "planning_stage": planning["stage"],
282
+ "collaboration_signals": collaboration["types"],
283
+ "complexity": complexity["level"],
284
+ "signals": all_signals,
285
+ "detailed_analysis": {
286
+ "urgency_breakdown": urgency,
287
+ "confidence_breakdown": confidence_info,
288
+ "tone_breakdown": tone,
289
+ "planning_breakdown": planning,
290
+ "collaboration_breakdown": collaboration,
291
+ "complexity_breakdown": complexity,
292
+ },
293
+ }
294
+
295
+ def _analyze_urgency(self, message: str) -> dict[str, Any]:
296
+ """Analyze urgency level from message content.
297
+
298
+ Args:
299
+ message: Lowercase commit message
300
+
301
+ Returns:
302
+ Dictionary with urgency analysis
303
+ """
304
+ signals = []
305
+ urgency_scores = defaultdict(float)
306
+
307
+ # Check configured urgency keywords
308
+ for urgency_level, keywords in self.urgency_keywords.items():
309
+ for keyword in keywords:
310
+ if keyword.lower() in message:
311
+ signals.append(f"urgency:{urgency_level}:{keyword}")
312
+ urgency_scores[urgency_level] += 1.0
313
+
314
+ # Additional urgency patterns
315
+ urgent_patterns = [
316
+ (r"\b(urgent|critical|emergency|asap|immediate)\b", "critical", 2.0),
317
+ (r"\b(important|priority|needed|required)\b", "important", 1.5),
318
+ (r"\b(hotfix|quickfix|patch)\b", "critical", 2.0),
319
+ (r"\b(breaking|major)\b", "important", 1.5),
320
+ (r"\b(minor|small|tiny)\b", "routine", 0.5),
321
+ ]
322
+
323
+ for pattern, level, weight in urgent_patterns:
324
+ if re.search(pattern, message):
325
+ signals.append(f"urgency_pattern:{level}:{pattern}")
326
+ urgency_scores[level] += weight
327
+
328
+ # Determine dominant urgency level
329
+ if urgency_scores:
330
+ dominant_urgency = max(urgency_scores.keys(), key=lambda k: urgency_scores[k])
331
+ else:
332
+ dominant_urgency = "routine"
333
+
334
+ return {"level": dominant_urgency, "scores": dict(urgency_scores), "signals": signals}
335
+
336
+ def _analyze_confidence(self, message: str, doc: Doc) -> dict[str, Any]:
337
+ """Analyze confidence level in the commit.
338
+
339
+ Args:
340
+ message: Lowercase commit message
341
+ doc: spaCy processed document
342
+
343
+ Returns:
344
+ Dictionary with confidence analysis
345
+ """
346
+ signals = []
347
+ confidence_score = 0.5 # Start with neutral confidence
348
+
349
+ # Check confidence indicators
350
+ for confidence_type, keywords in self.confidence_indicators.items():
351
+ matches = sum(1 for keyword in keywords if keyword in message)
352
+ if matches > 0:
353
+ signals.append(f"confidence:{confidence_type}:{matches}")
354
+
355
+ if confidence_type == "high_confidence":
356
+ confidence_score += matches * 0.2
357
+ elif confidence_type == "low_confidence":
358
+ confidence_score -= matches * 0.15
359
+ elif confidence_type == "uncertain":
360
+ confidence_score -= matches * 0.25
361
+
362
+ # Check message structure and completeness
363
+ if len(message.split()) >= 5: # Detailed message
364
+ confidence_score += 0.1
365
+ signals.append("confidence:detailed_message")
366
+ elif len(message.split()) <= 2: # Very brief message
367
+ confidence_score -= 0.1
368
+ signals.append("confidence:brief_message")
369
+
370
+ # Check for question marks (uncertainty)
371
+ if "?" in message:
372
+ confidence_score -= 0.2
373
+ signals.append("confidence:contains_question")
374
+
375
+ # Check for ellipsis or incomplete thoughts
376
+ if "..." in message or message.endswith("."):
377
+ confidence_score -= 0.1
378
+ signals.append("confidence:incomplete_thought")
379
+
380
+ # Normalize confidence score
381
+ confidence_score = max(0.0, min(1.0, confidence_score))
382
+
383
+ return {
384
+ "score": confidence_score,
385
+ "level": (
386
+ "high" if confidence_score > 0.7 else "medium" if confidence_score > 0.4 else "low"
387
+ ),
388
+ "signals": signals,
389
+ }
390
+
391
+ def _analyze_tone(self, message: str) -> dict[str, Any]:
392
+ """Analyze emotional tone of the commit message.
393
+
394
+ Args:
395
+ message: Lowercase commit message
396
+
397
+ Returns:
398
+ Dictionary with tone analysis
399
+ """
400
+ signals = []
401
+ tone_scores = defaultdict(float)
402
+
403
+ # Check tone indicators
404
+ for tone_type, keywords in self.tone_indicators.items():
405
+ matches = sum(1 for keyword in keywords if keyword in message)
406
+ if matches > 0:
407
+ signals.append(f"tone:{tone_type}:{matches}")
408
+ tone_scores[tone_type] += matches
409
+
410
+ # Check punctuation for tone
411
+ if "!" in message:
412
+ tone_scores["confident"] += 0.5
413
+ signals.append("tone:exclamation_mark")
414
+ elif "..." in message:
415
+ tone_scores["cautious"] += 0.5
416
+ signals.append("tone:ellipsis")
417
+
418
+ # Determine dominant tone
419
+ if tone_scores:
420
+ dominant_tone = max(tone_scores.keys(), key=lambda k: tone_scores[k])
421
+ else:
422
+ dominant_tone = "neutral"
423
+
424
+ return {"dominant_tone": dominant_tone, "scores": dict(tone_scores), "signals": signals}
425
+
426
+ def _analyze_planning_stage(self, message: str) -> dict[str, Any]:
427
+ """Analyze what stage of planning/development this commit represents.
428
+
429
+ Args:
430
+ message: Lowercase commit message
431
+
432
+ Returns:
433
+ Dictionary with planning stage analysis
434
+ """
435
+ signals = []
436
+ stage_scores = defaultdict(float)
437
+
438
+ # Check planning indicators
439
+ for stage_type, keywords in self.planning_indicators.items():
440
+ matches = sum(1 for keyword in keywords if keyword in message)
441
+ if matches > 0:
442
+ signals.append(f"planning:{stage_type}:{matches}")
443
+ stage_scores[stage_type] += matches
444
+
445
+ # Additional stage indicators
446
+ if any(word in message for word in ["start", "initial", "begin", "setup"]):
447
+ stage_scores["initial"] = stage_scores.get("initial", 0) + 1
448
+ signals.append("planning:initial_stage")
449
+
450
+ if any(word in message for word in ["complete", "finish", "done", "final"]):
451
+ stage_scores["completion"] = stage_scores.get("completion", 0) + 1
452
+ signals.append("planning:completion_stage")
453
+
454
+ # Determine stage
455
+ if stage_scores:
456
+ if "immediate" in stage_scores:
457
+ stage = "immediate"
458
+ elif "todo" in stage_scores:
459
+ stage = "planning"
460
+ elif "future_work" in stage_scores:
461
+ stage = "future_planning"
462
+ elif "completion" in stage_scores:
463
+ stage = "completion"
464
+ elif "initial" in stage_scores:
465
+ stage = "initiation"
466
+ else:
467
+ stage = "implementation"
468
+ else:
469
+ stage = "implementation"
470
+
471
+ return {"stage": stage, "scores": dict(stage_scores), "signals": signals}
472
+
473
+ def _analyze_collaboration(self, message: str) -> dict[str, Any]:
474
+ """Analyze collaboration signals in the commit message.
475
+
476
+ Args:
477
+ message: Lowercase commit message
478
+
479
+ Returns:
480
+ Dictionary with collaboration analysis
481
+ """
482
+ signals = []
483
+ collaboration_types = []
484
+
485
+ # Check collaboration indicators
486
+ for collab_type, keywords in self.collaboration_indicators.items():
487
+ matches = [keyword for keyword in keywords if keyword in message]
488
+ if matches:
489
+ signals.extend([f"collaboration:{collab_type}:{match}" for match in matches])
490
+ collaboration_types.append(collab_type)
491
+
492
+ # Check for co-author patterns
493
+ if "co-authored-by:" in message or "with @" in message:
494
+ collaboration_types.append("co_authored")
495
+ signals.append("collaboration:co_authored")
496
+
497
+ return {
498
+ "types": collaboration_types,
499
+ "signals": signals,
500
+ "is_collaborative": len(collaboration_types) > 0,
501
+ }
502
+
503
+ def _analyze_complexity(self, message: str) -> dict[str, Any]:
504
+ """Analyze technical complexity signals in the commit message.
505
+
506
+ Args:
507
+ message: Lowercase commit message
508
+
509
+ Returns:
510
+ Dictionary with complexity analysis
511
+ """
512
+ signals = []
513
+ complexity_scores = defaultdict(float)
514
+
515
+ # Check complexity indicators
516
+ for complexity_type, keywords in self.complexity_indicators.items():
517
+ matches = sum(1 for keyword in keywords if keyword in message)
518
+ if matches > 0:
519
+ signals.append(f"complexity:{complexity_type}:{matches}")
520
+ complexity_scores[complexity_type] += matches
521
+
522
+ # Determine complexity level
523
+ if complexity_scores:
524
+ if complexity_scores.get("complex", 0) > complexity_scores.get("simple", 0):
525
+ level = "complex"
526
+ elif complexity_scores.get("simple", 0) > 0:
527
+ level = "simple"
528
+ elif complexity_scores.get("refactoring", 0) > 0:
529
+ level = "moderate" # Refactoring is usually moderate complexity
530
+ else:
531
+ level = "moderate"
532
+ else:
533
+ level = "moderate"
534
+
535
+ return {"level": level, "scores": dict(complexity_scores), "signals": signals}
@@ -0,0 +1,35 @@
1
+ """LLM classifier module components.
2
+
3
+ This module provides modular components for LLM-based commit classification.
4
+ """
5
+
6
+ from .base import BaseLLMClassifier, ClassificationResult, LLMProviderConfig
7
+ from .batch_processor import BatchConfig, BatchProcessor, BatchResult
8
+ from .cache import LLMCache
9
+ from .cost_tracker import CostRecord, CostTracker, ModelPricing
10
+ from .openai_client import OpenAIClassifier, OpenAIConfig
11
+ from .prompts import PromptGenerator, PromptTemplate, PromptVersion
12
+ from .response_parser import ResponseParser
13
+
14
+ __all__ = [
15
+ # Base classes
16
+ "BaseLLMClassifier",
17
+ "ClassificationResult",
18
+ "LLMProviderConfig",
19
+ # Prompts
20
+ "PromptGenerator",
21
+ "PromptVersion",
22
+ "PromptTemplate",
23
+ # Providers
24
+ "OpenAIClassifier",
25
+ "OpenAIConfig",
26
+ # Components
27
+ "ResponseParser",
28
+ "CostTracker",
29
+ "ModelPricing",
30
+ "CostRecord",
31
+ "BatchProcessor",
32
+ "BatchConfig",
33
+ "BatchResult",
34
+ "LLMCache",
35
+ ]