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