mcp-ticketer 0.3.0__py3-none-any.whl → 2.2.9__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.
- mcp_ticketer/__init__.py +10 -10
- mcp_ticketer/__version__.py +3 -3
- mcp_ticketer/_version_scm.py +1 -0
- mcp_ticketer/adapters/__init__.py +2 -0
- mcp_ticketer/adapters/aitrackdown.py +930 -52
- mcp_ticketer/adapters/asana/__init__.py +15 -0
- mcp_ticketer/adapters/asana/adapter.py +1537 -0
- mcp_ticketer/adapters/asana/client.py +292 -0
- mcp_ticketer/adapters/asana/mappers.py +348 -0
- mcp_ticketer/adapters/asana/types.py +146 -0
- mcp_ticketer/adapters/github/__init__.py +26 -0
- mcp_ticketer/adapters/github/adapter.py +3229 -0
- mcp_ticketer/adapters/github/client.py +335 -0
- mcp_ticketer/adapters/github/mappers.py +797 -0
- mcp_ticketer/adapters/github/queries.py +692 -0
- mcp_ticketer/adapters/github/types.py +460 -0
- mcp_ticketer/adapters/hybrid.py +58 -16
- mcp_ticketer/adapters/jira/__init__.py +35 -0
- mcp_ticketer/adapters/jira/adapter.py +1351 -0
- mcp_ticketer/adapters/jira/client.py +271 -0
- mcp_ticketer/adapters/jira/mappers.py +246 -0
- mcp_ticketer/adapters/jira/queries.py +216 -0
- mcp_ticketer/adapters/jira/types.py +304 -0
- mcp_ticketer/adapters/linear/__init__.py +1 -1
- mcp_ticketer/adapters/linear/adapter.py +3810 -462
- mcp_ticketer/adapters/linear/client.py +312 -69
- mcp_ticketer/adapters/linear/mappers.py +305 -85
- mcp_ticketer/adapters/linear/queries.py +317 -17
- mcp_ticketer/adapters/linear/types.py +187 -64
- mcp_ticketer/adapters/linear.py +2 -2
- mcp_ticketer/analysis/__init__.py +56 -0
- mcp_ticketer/analysis/dependency_graph.py +255 -0
- mcp_ticketer/analysis/health_assessment.py +304 -0
- mcp_ticketer/analysis/orphaned.py +218 -0
- mcp_ticketer/analysis/project_status.py +594 -0
- mcp_ticketer/analysis/similarity.py +224 -0
- mcp_ticketer/analysis/staleness.py +266 -0
- mcp_ticketer/automation/__init__.py +11 -0
- mcp_ticketer/automation/project_updates.py +378 -0
- mcp_ticketer/cache/memory.py +9 -8
- mcp_ticketer/cli/adapter_diagnostics.py +91 -54
- mcp_ticketer/cli/auggie_configure.py +116 -15
- mcp_ticketer/cli/codex_configure.py +274 -82
- mcp_ticketer/cli/configure.py +1323 -151
- mcp_ticketer/cli/cursor_configure.py +314 -0
- mcp_ticketer/cli/diagnostics.py +209 -114
- mcp_ticketer/cli/discover.py +297 -26
- mcp_ticketer/cli/gemini_configure.py +119 -26
- mcp_ticketer/cli/init_command.py +880 -0
- mcp_ticketer/cli/install_mcp_server.py +418 -0
- mcp_ticketer/cli/instruction_commands.py +435 -0
- mcp_ticketer/cli/linear_commands.py +256 -130
- mcp_ticketer/cli/main.py +140 -1544
- mcp_ticketer/cli/mcp_configure.py +1013 -100
- mcp_ticketer/cli/mcp_server_commands.py +415 -0
- mcp_ticketer/cli/migrate_config.py +12 -8
- mcp_ticketer/cli/platform_commands.py +123 -0
- mcp_ticketer/cli/platform_detection.py +477 -0
- mcp_ticketer/cli/platform_installer.py +545 -0
- mcp_ticketer/cli/project_update_commands.py +350 -0
- mcp_ticketer/cli/python_detection.py +126 -0
- mcp_ticketer/cli/queue_commands.py +15 -15
- mcp_ticketer/cli/setup_command.py +794 -0
- mcp_ticketer/cli/simple_health.py +84 -59
- mcp_ticketer/cli/ticket_commands.py +1375 -0
- mcp_ticketer/cli/update_checker.py +313 -0
- mcp_ticketer/cli/utils.py +195 -72
- mcp_ticketer/core/__init__.py +64 -1
- mcp_ticketer/core/adapter.py +618 -18
- mcp_ticketer/core/config.py +77 -68
- mcp_ticketer/core/env_discovery.py +75 -16
- mcp_ticketer/core/env_loader.py +121 -97
- mcp_ticketer/core/exceptions.py +32 -24
- mcp_ticketer/core/http_client.py +26 -26
- mcp_ticketer/core/instructions.py +405 -0
- mcp_ticketer/core/label_manager.py +732 -0
- mcp_ticketer/core/mappers.py +42 -30
- mcp_ticketer/core/milestone_manager.py +252 -0
- mcp_ticketer/core/models.py +566 -19
- mcp_ticketer/core/onepassword_secrets.py +379 -0
- mcp_ticketer/core/priority_matcher.py +463 -0
- mcp_ticketer/core/project_config.py +189 -49
- mcp_ticketer/core/project_utils.py +281 -0
- mcp_ticketer/core/project_validator.py +376 -0
- mcp_ticketer/core/registry.py +3 -3
- mcp_ticketer/core/session_state.py +176 -0
- mcp_ticketer/core/state_matcher.py +592 -0
- mcp_ticketer/core/url_parser.py +425 -0
- mcp_ticketer/core/validators.py +69 -0
- mcp_ticketer/defaults/ticket_instructions.md +644 -0
- mcp_ticketer/mcp/__init__.py +29 -1
- mcp_ticketer/mcp/__main__.py +60 -0
- mcp_ticketer/mcp/server/__init__.py +25 -0
- mcp_ticketer/mcp/server/__main__.py +60 -0
- mcp_ticketer/mcp/server/constants.py +58 -0
- mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
- mcp_ticketer/mcp/server/dto.py +195 -0
- mcp_ticketer/mcp/server/main.py +1343 -0
- mcp_ticketer/mcp/server/response_builder.py +206 -0
- mcp_ticketer/mcp/server/routing.py +723 -0
- mcp_ticketer/mcp/server/server_sdk.py +151 -0
- mcp_ticketer/mcp/server/tools/__init__.py +69 -0
- mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +224 -0
- mcp_ticketer/mcp/server/tools/bulk_tools.py +330 -0
- mcp_ticketer/mcp/server/tools/comment_tools.py +152 -0
- mcp_ticketer/mcp/server/tools/config_tools.py +1564 -0
- mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py +942 -0
- mcp_ticketer/mcp/server/tools/instruction_tools.py +295 -0
- mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
- mcp_ticketer/mcp/server/tools/milestone_tools.py +338 -0
- mcp_ticketer/mcp/server/tools/pr_tools.py +150 -0
- mcp_ticketer/mcp/server/tools/project_status_tools.py +158 -0
- mcp_ticketer/mcp/server/tools/project_update_tools.py +473 -0
- mcp_ticketer/mcp/server/tools/search_tools.py +318 -0
- mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
- mcp_ticketer/mcp/server/tools/ticket_tools.py +1413 -0
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +364 -0
- mcp_ticketer/queue/__init__.py +1 -0
- mcp_ticketer/queue/health_monitor.py +168 -136
- mcp_ticketer/queue/manager.py +78 -63
- mcp_ticketer/queue/queue.py +108 -21
- mcp_ticketer/queue/run_worker.py +2 -2
- mcp_ticketer/queue/ticket_registry.py +213 -155
- mcp_ticketer/queue/worker.py +96 -58
- mcp_ticketer/utils/__init__.py +5 -0
- mcp_ticketer/utils/token_utils.py +246 -0
- mcp_ticketer-2.2.9.dist-info/METADATA +1396 -0
- mcp_ticketer-2.2.9.dist-info/RECORD +158 -0
- mcp_ticketer-2.2.9.dist-info/top_level.txt +2 -0
- py_mcp_installer/examples/phase3_demo.py +178 -0
- py_mcp_installer/scripts/manage_version.py +54 -0
- py_mcp_installer/setup.py +6 -0
- py_mcp_installer/src/py_mcp_installer/__init__.py +153 -0
- py_mcp_installer/src/py_mcp_installer/command_builder.py +445 -0
- py_mcp_installer/src/py_mcp_installer/config_manager.py +541 -0
- py_mcp_installer/src/py_mcp_installer/exceptions.py +243 -0
- py_mcp_installer/src/py_mcp_installer/installation_strategy.py +617 -0
- py_mcp_installer/src/py_mcp_installer/installer.py +656 -0
- py_mcp_installer/src/py_mcp_installer/mcp_inspector.py +750 -0
- py_mcp_installer/src/py_mcp_installer/platform_detector.py +451 -0
- py_mcp_installer/src/py_mcp_installer/platforms/__init__.py +26 -0
- py_mcp_installer/src/py_mcp_installer/platforms/claude_code.py +225 -0
- py_mcp_installer/src/py_mcp_installer/platforms/codex.py +181 -0
- py_mcp_installer/src/py_mcp_installer/platforms/cursor.py +191 -0
- py_mcp_installer/src/py_mcp_installer/types.py +222 -0
- py_mcp_installer/src/py_mcp_installer/utils.py +463 -0
- py_mcp_installer/tests/__init__.py +0 -0
- py_mcp_installer/tests/platforms/__init__.py +0 -0
- py_mcp_installer/tests/test_platform_detector.py +17 -0
- mcp_ticketer/adapters/github.py +0 -1354
- mcp_ticketer/adapters/jira.py +0 -1011
- mcp_ticketer/mcp/server.py +0 -2030
- mcp_ticketer-0.3.0.dist-info/METADATA +0 -414
- mcp_ticketer-0.3.0.dist-info/RECORD +0 -59
- mcp_ticketer-0.3.0.dist-info/top_level.txt +0 -1
- {mcp_ticketer-0.3.0.dist-info → mcp_ticketer-2.2.9.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.3.0.dist-info → mcp_ticketer-2.2.9.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.3.0.dist-info → mcp_ticketer-2.2.9.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,47 +3,47 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from enum import Enum
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import Any
|
|
7
7
|
|
|
8
8
|
from mcp_ticketer.core.models import Priority, TicketState
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class LinearPriorityMapping:
|
|
12
12
|
"""Mapping between universal Priority and Linear priority values."""
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
# Linear uses numeric priorities: 0=No priority, 1=Urgent, 2=High, 3=Medium, 4=Low
|
|
15
|
-
TO_LINEAR:
|
|
15
|
+
TO_LINEAR: dict[Priority, int] = {
|
|
16
16
|
Priority.CRITICAL: 1, # Urgent
|
|
17
|
-
Priority.HIGH: 2,
|
|
18
|
-
Priority.MEDIUM: 3,
|
|
19
|
-
Priority.LOW: 4,
|
|
17
|
+
Priority.HIGH: 2, # High
|
|
18
|
+
Priority.MEDIUM: 3, # Medium
|
|
19
|
+
Priority.LOW: 4, # Low
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
FROM_LINEAR:
|
|
23
|
-
0: Priority.LOW,
|
|
21
|
+
|
|
22
|
+
FROM_LINEAR: dict[int, Priority] = {
|
|
23
|
+
0: Priority.LOW, # No priority -> Low
|
|
24
24
|
1: Priority.CRITICAL, # Urgent -> Critical
|
|
25
|
-
2: Priority.HIGH,
|
|
26
|
-
3: Priority.MEDIUM,
|
|
27
|
-
4: Priority.LOW,
|
|
25
|
+
2: Priority.HIGH, # High -> High
|
|
26
|
+
3: Priority.MEDIUM, # Medium -> Medium
|
|
27
|
+
4: Priority.LOW, # Low -> Low
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
class LinearStateMapping:
|
|
32
32
|
"""Mapping between universal TicketState and Linear workflow state types."""
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
# Linear workflow state types
|
|
35
|
-
TO_LINEAR:
|
|
35
|
+
TO_LINEAR: dict[TicketState, str] = {
|
|
36
36
|
TicketState.OPEN: "unstarted",
|
|
37
37
|
TicketState.IN_PROGRESS: "started",
|
|
38
38
|
TicketState.READY: "unstarted", # No direct equivalent, use unstarted
|
|
39
|
-
TicketState.TESTED: "started",
|
|
39
|
+
TicketState.TESTED: "started", # No direct equivalent, use started
|
|
40
40
|
TicketState.DONE: "completed",
|
|
41
41
|
TicketState.CLOSED: "canceled",
|
|
42
42
|
TicketState.WAITING: "unstarted",
|
|
43
43
|
TicketState.BLOCKED: "unstarted",
|
|
44
44
|
}
|
|
45
|
-
|
|
46
|
-
FROM_LINEAR:
|
|
45
|
+
|
|
46
|
+
FROM_LINEAR: dict[str, TicketState] = {
|
|
47
47
|
"backlog": TicketState.OPEN,
|
|
48
48
|
"unstarted": TicketState.OPEN,
|
|
49
49
|
"started": TicketState.IN_PROGRESS,
|
|
@@ -51,10 +51,38 @@ class LinearStateMapping:
|
|
|
51
51
|
"canceled": TicketState.CLOSED,
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
# Semantic state name mappings for flexible workflow matching (1M-552)
|
|
55
|
+
# Maps universal states to common Linear state names (case-insensitive)
|
|
56
|
+
SEMANTIC_NAMES: dict[TicketState, list[str]] = {
|
|
57
|
+
TicketState.OPEN: ["todo", "to do", "open", "new", "backlog"],
|
|
58
|
+
TicketState.READY: ["ready", "triage", "ready for dev", "ready to start"],
|
|
59
|
+
TicketState.TESTED: [
|
|
60
|
+
"tested",
|
|
61
|
+
"in review",
|
|
62
|
+
"review",
|
|
63
|
+
"qa",
|
|
64
|
+
"testing",
|
|
65
|
+
"ready for review",
|
|
66
|
+
],
|
|
67
|
+
TicketState.WAITING: ["waiting", "on hold", "paused"],
|
|
68
|
+
TicketState.BLOCKED: ["blocked"],
|
|
69
|
+
TicketState.IN_PROGRESS: [
|
|
70
|
+
"in progress",
|
|
71
|
+
"in-progress",
|
|
72
|
+
"started",
|
|
73
|
+
"doing",
|
|
74
|
+
"active",
|
|
75
|
+
"in development",
|
|
76
|
+
"in dev",
|
|
77
|
+
],
|
|
78
|
+
TicketState.DONE: ["done", "completed", "finished"],
|
|
79
|
+
TicketState.CLOSED: ["closed", "canceled", "cancelled", "won't do", "wont do"],
|
|
80
|
+
}
|
|
81
|
+
|
|
54
82
|
|
|
55
83
|
class LinearWorkflowStateType(Enum):
|
|
56
84
|
"""Linear workflow state types."""
|
|
57
|
-
|
|
85
|
+
|
|
58
86
|
BACKLOG = "backlog"
|
|
59
87
|
UNSTARTED = "unstarted"
|
|
60
88
|
STARTED = "started"
|
|
@@ -64,7 +92,7 @@ class LinearWorkflowStateType(Enum):
|
|
|
64
92
|
|
|
65
93
|
class LinearProjectState(Enum):
|
|
66
94
|
"""Linear project states."""
|
|
67
|
-
|
|
95
|
+
|
|
68
96
|
PLANNED = "planned"
|
|
69
97
|
STARTED = "started"
|
|
70
98
|
COMPLETED = "completed"
|
|
@@ -74,7 +102,7 @@ class LinearProjectState(Enum):
|
|
|
74
102
|
|
|
75
103
|
class LinearIssueRelationType(Enum):
|
|
76
104
|
"""Linear issue relation types."""
|
|
77
|
-
|
|
105
|
+
|
|
78
106
|
BLOCKS = "blocks"
|
|
79
107
|
BLOCKED_BY = "blockedBy"
|
|
80
108
|
DUPLICATE = "duplicate"
|
|
@@ -84,57 +112,143 @@ class LinearIssueRelationType(Enum):
|
|
|
84
112
|
|
|
85
113
|
class LinearCommentType(Enum):
|
|
86
114
|
"""Linear comment types."""
|
|
87
|
-
|
|
115
|
+
|
|
88
116
|
COMMENT = "comment"
|
|
89
117
|
SYSTEM = "system"
|
|
90
118
|
|
|
91
119
|
|
|
92
120
|
def get_linear_priority(priority: Priority) -> int:
|
|
93
121
|
"""Convert universal Priority to Linear priority value.
|
|
94
|
-
|
|
122
|
+
|
|
95
123
|
Args:
|
|
96
124
|
priority: Universal priority enum
|
|
97
|
-
|
|
125
|
+
|
|
98
126
|
Returns:
|
|
99
127
|
Linear priority integer (0-4)
|
|
128
|
+
|
|
100
129
|
"""
|
|
101
130
|
return LinearPriorityMapping.TO_LINEAR.get(priority, 3) # Default to Medium
|
|
102
131
|
|
|
103
132
|
|
|
104
133
|
def get_universal_priority(linear_priority: int) -> Priority:
|
|
105
134
|
"""Convert Linear priority value to universal Priority.
|
|
106
|
-
|
|
135
|
+
|
|
107
136
|
Args:
|
|
108
137
|
linear_priority: Linear priority integer (0-4)
|
|
109
|
-
|
|
138
|
+
|
|
110
139
|
Returns:
|
|
111
140
|
Universal priority enum
|
|
141
|
+
|
|
112
142
|
"""
|
|
113
143
|
return LinearPriorityMapping.FROM_LINEAR.get(linear_priority, Priority.MEDIUM)
|
|
114
144
|
|
|
115
145
|
|
|
116
146
|
def get_linear_state_type(state: TicketState) -> str:
|
|
117
147
|
"""Convert universal TicketState to Linear workflow state type.
|
|
118
|
-
|
|
148
|
+
|
|
119
149
|
Args:
|
|
120
150
|
state: Universal ticket state enum
|
|
121
|
-
|
|
151
|
+
|
|
122
152
|
Returns:
|
|
123
153
|
Linear workflow state type string
|
|
154
|
+
|
|
124
155
|
"""
|
|
125
156
|
return LinearStateMapping.TO_LINEAR.get(state, "unstarted")
|
|
126
157
|
|
|
127
158
|
|
|
128
|
-
def get_universal_state(
|
|
129
|
-
|
|
130
|
-
|
|
159
|
+
def get_universal_state(
|
|
160
|
+
linear_state_type: str, state_name: str | None = None
|
|
161
|
+
) -> TicketState:
|
|
162
|
+
"""Convert Linear workflow state type to universal TicketState with synonym matching.
|
|
163
|
+
|
|
164
|
+
This function implements intelligent state mapping with fallback strategies:
|
|
165
|
+
1. Try exact match on state type (backlog, unstarted, started, completed, canceled)
|
|
166
|
+
2. Try synonym matching on state name (ToDo, In Review, Testing, etc.)
|
|
167
|
+
3. Default to OPEN for unknown states
|
|
168
|
+
|
|
169
|
+
Synonym Matching Rules (ticket 1M-164, fixed in v2.0.4):
|
|
170
|
+
- "done", "completed", "finished", "resolved" → DONE
|
|
171
|
+
- "closed", "canceled", "cancelled", "won't do" → CLOSED
|
|
172
|
+
- Everything else → OPEN
|
|
173
|
+
|
|
131
174
|
Args:
|
|
132
|
-
linear_state_type: Linear workflow state type string
|
|
133
|
-
|
|
175
|
+
linear_state_type: Linear workflow state type string (from state.type field)
|
|
176
|
+
state_name: Linear workflow state name (from state.name field, optional)
|
|
177
|
+
|
|
134
178
|
Returns:
|
|
135
179
|
Universal ticket state enum
|
|
180
|
+
|
|
136
181
|
"""
|
|
137
|
-
|
|
182
|
+
# First try exact type match
|
|
183
|
+
if linear_state_type in LinearStateMapping.FROM_LINEAR:
|
|
184
|
+
return LinearStateMapping.FROM_LINEAR[linear_state_type]
|
|
185
|
+
|
|
186
|
+
# If no exact match and state_name provided, try synonym matching
|
|
187
|
+
if state_name:
|
|
188
|
+
state_name_lower = state_name.lower().strip()
|
|
189
|
+
|
|
190
|
+
# DONE states: Work successfully completed
|
|
191
|
+
# - User finished the work
|
|
192
|
+
# - Requirements met
|
|
193
|
+
# - Quality verified
|
|
194
|
+
done_synonyms = [
|
|
195
|
+
"done",
|
|
196
|
+
"completed",
|
|
197
|
+
"finished",
|
|
198
|
+
"resolved",
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
if any(synonym in state_name_lower for synonym in done_synonyms):
|
|
202
|
+
return TicketState.DONE
|
|
203
|
+
|
|
204
|
+
# CLOSED states: Work terminated without completion
|
|
205
|
+
# - User decided not to do it
|
|
206
|
+
# - Requirements changed
|
|
207
|
+
# - Duplicate/invalid ticket
|
|
208
|
+
closed_synonyms = [
|
|
209
|
+
"closed",
|
|
210
|
+
"cancelled",
|
|
211
|
+
"canceled",
|
|
212
|
+
"won't do",
|
|
213
|
+
"wont do",
|
|
214
|
+
"rejected",
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
if any(synonym in state_name_lower for synonym in closed_synonyms):
|
|
218
|
+
return TicketState.CLOSED
|
|
219
|
+
|
|
220
|
+
# Check for "in progress" synonyms
|
|
221
|
+
in_progress_synonyms = [
|
|
222
|
+
"in progress",
|
|
223
|
+
"in-progress",
|
|
224
|
+
"working",
|
|
225
|
+
"active",
|
|
226
|
+
"started",
|
|
227
|
+
"doing",
|
|
228
|
+
"in development",
|
|
229
|
+
"in dev",
|
|
230
|
+
]
|
|
231
|
+
|
|
232
|
+
if any(synonym in state_name_lower for synonym in in_progress_synonyms):
|
|
233
|
+
return TicketState.IN_PROGRESS
|
|
234
|
+
|
|
235
|
+
# Check for "review/testing" synonyms
|
|
236
|
+
review_synonyms = [
|
|
237
|
+
"review",
|
|
238
|
+
"in review",
|
|
239
|
+
"in-review",
|
|
240
|
+
"testing",
|
|
241
|
+
"in test",
|
|
242
|
+
"in-test",
|
|
243
|
+
"qa",
|
|
244
|
+
"ready for review",
|
|
245
|
+
]
|
|
246
|
+
|
|
247
|
+
if any(synonym in state_name_lower for synonym in review_synonyms):
|
|
248
|
+
return TicketState.READY
|
|
249
|
+
|
|
250
|
+
# Default: everything else is OPEN (including "ToDo", "Backlog", "To Do", etc.)
|
|
251
|
+
return TicketState.OPEN
|
|
138
252
|
|
|
139
253
|
|
|
140
254
|
def build_issue_filter(
|
|
@@ -143,57 +257,64 @@ def build_issue_filter(
|
|
|
143
257
|
priority: Priority | None = None,
|
|
144
258
|
team_id: str | None = None,
|
|
145
259
|
project_id: str | None = None,
|
|
260
|
+
parent_id: str | None = None,
|
|
146
261
|
labels: list[str] | None = None,
|
|
147
262
|
created_after: str | None = None,
|
|
148
263
|
updated_after: str | None = None,
|
|
149
264
|
due_before: str | None = None,
|
|
150
265
|
include_archived: bool = False,
|
|
151
|
-
) ->
|
|
266
|
+
) -> dict[str, Any]:
|
|
152
267
|
"""Build a Linear issue filter from parameters.
|
|
153
|
-
|
|
268
|
+
|
|
154
269
|
Args:
|
|
155
270
|
state: Filter by ticket state
|
|
156
271
|
assignee_id: Filter by assignee Linear user ID
|
|
157
272
|
priority: Filter by priority
|
|
158
273
|
team_id: Filter by team ID
|
|
159
274
|
project_id: Filter by project ID
|
|
275
|
+
parent_id: Filter by parent issue ID (for listing sub-issues)
|
|
160
276
|
labels: Filter by label names
|
|
161
277
|
created_after: Filter by creation date (ISO string)
|
|
162
278
|
updated_after: Filter by update date (ISO string)
|
|
163
279
|
due_before: Filter by due date (ISO string)
|
|
164
280
|
include_archived: Whether to include archived issues
|
|
165
|
-
|
|
281
|
+
|
|
166
282
|
Returns:
|
|
167
283
|
Linear GraphQL filter object
|
|
284
|
+
|
|
168
285
|
"""
|
|
169
|
-
issue_filter:
|
|
170
|
-
|
|
286
|
+
issue_filter: dict[str, Any] = {}
|
|
287
|
+
|
|
171
288
|
# Team filter (required for most operations)
|
|
172
289
|
if team_id:
|
|
173
290
|
issue_filter["team"] = {"id": {"eq": team_id}}
|
|
174
|
-
|
|
291
|
+
|
|
175
292
|
# State filter
|
|
176
293
|
if state:
|
|
177
294
|
state_type = get_linear_state_type(state)
|
|
178
295
|
issue_filter["state"] = {"type": {"eq": state_type}}
|
|
179
|
-
|
|
296
|
+
|
|
180
297
|
# Assignee filter
|
|
181
298
|
if assignee_id:
|
|
182
299
|
issue_filter["assignee"] = {"id": {"eq": assignee_id}}
|
|
183
|
-
|
|
300
|
+
|
|
184
301
|
# Priority filter
|
|
185
302
|
if priority:
|
|
186
303
|
linear_priority = get_linear_priority(priority)
|
|
187
304
|
issue_filter["priority"] = {"eq": linear_priority}
|
|
188
|
-
|
|
305
|
+
|
|
189
306
|
# Project filter
|
|
190
307
|
if project_id:
|
|
191
308
|
issue_filter["project"] = {"id": {"eq": project_id}}
|
|
192
|
-
|
|
309
|
+
|
|
310
|
+
# Parent filter (for listing children/sub-issues)
|
|
311
|
+
if parent_id:
|
|
312
|
+
issue_filter["parent"] = {"id": {"eq": parent_id}}
|
|
313
|
+
|
|
193
314
|
# Labels filter
|
|
194
315
|
if labels:
|
|
195
316
|
issue_filter["labels"] = {"some": {"name": {"in": labels}}}
|
|
196
|
-
|
|
317
|
+
|
|
197
318
|
# Date filters
|
|
198
319
|
if created_after:
|
|
199
320
|
issue_filter["createdAt"] = {"gte": created_after}
|
|
@@ -201,11 +322,11 @@ def build_issue_filter(
|
|
|
201
322
|
issue_filter["updatedAt"] = {"gte": updated_after}
|
|
202
323
|
if due_before:
|
|
203
324
|
issue_filter["dueDate"] = {"lte": due_before}
|
|
204
|
-
|
|
325
|
+
|
|
205
326
|
# Archived filter
|
|
206
327
|
if not include_archived:
|
|
207
328
|
issue_filter["archivedAt"] = {"null": True}
|
|
208
|
-
|
|
329
|
+
|
|
209
330
|
return issue_filter
|
|
210
331
|
|
|
211
332
|
|
|
@@ -213,65 +334,67 @@ def build_project_filter(
|
|
|
213
334
|
state: str | None = None,
|
|
214
335
|
team_id: str | None = None,
|
|
215
336
|
include_completed: bool = True,
|
|
216
|
-
) ->
|
|
337
|
+
) -> dict[str, Any]:
|
|
217
338
|
"""Build a Linear project filter from parameters.
|
|
218
|
-
|
|
339
|
+
|
|
219
340
|
Args:
|
|
220
341
|
state: Filter by project state
|
|
221
342
|
team_id: Filter by team ID
|
|
222
343
|
include_completed: Whether to include completed projects
|
|
223
|
-
|
|
344
|
+
|
|
224
345
|
Returns:
|
|
225
346
|
Linear GraphQL filter object
|
|
347
|
+
|
|
226
348
|
"""
|
|
227
|
-
project_filter:
|
|
228
|
-
|
|
349
|
+
project_filter: dict[str, Any] = {}
|
|
350
|
+
|
|
229
351
|
# Team filter
|
|
230
352
|
if team_id:
|
|
231
353
|
project_filter["teams"] = {"some": {"id": {"eq": team_id}}}
|
|
232
|
-
|
|
354
|
+
|
|
233
355
|
# State filter
|
|
234
356
|
if state:
|
|
235
357
|
project_filter["state"] = {"eq": state}
|
|
236
358
|
elif not include_completed:
|
|
237
359
|
# Exclude completed projects by default
|
|
238
360
|
project_filter["state"] = {"neq": "completed"}
|
|
239
|
-
|
|
361
|
+
|
|
240
362
|
return project_filter
|
|
241
363
|
|
|
242
364
|
|
|
243
|
-
def extract_linear_metadata(issue_data:
|
|
365
|
+
def extract_linear_metadata(issue_data: dict[str, Any]) -> dict[str, Any]:
|
|
244
366
|
"""Extract Linear-specific metadata from issue data.
|
|
245
|
-
|
|
367
|
+
|
|
246
368
|
Args:
|
|
247
369
|
issue_data: Raw Linear issue data from GraphQL
|
|
248
|
-
|
|
370
|
+
|
|
249
371
|
Returns:
|
|
250
372
|
Dictionary of Linear-specific metadata
|
|
373
|
+
|
|
251
374
|
"""
|
|
252
375
|
metadata = {}
|
|
253
|
-
|
|
376
|
+
|
|
254
377
|
# Extract Linear-specific fields
|
|
255
378
|
if "dueDate" in issue_data and issue_data["dueDate"]:
|
|
256
379
|
metadata["due_date"] = issue_data["dueDate"]
|
|
257
|
-
|
|
380
|
+
|
|
258
381
|
if "cycle" in issue_data and issue_data["cycle"]:
|
|
259
382
|
metadata["cycle_id"] = issue_data["cycle"]["id"]
|
|
260
383
|
metadata["cycle_name"] = issue_data["cycle"]["name"]
|
|
261
|
-
|
|
384
|
+
|
|
262
385
|
if "estimate" in issue_data and issue_data["estimate"]:
|
|
263
386
|
metadata["estimate"] = issue_data["estimate"]
|
|
264
|
-
|
|
387
|
+
|
|
265
388
|
if "branchName" in issue_data and issue_data["branchName"]:
|
|
266
389
|
metadata["branch_name"] = issue_data["branchName"]
|
|
267
|
-
|
|
390
|
+
|
|
268
391
|
if "url" in issue_data:
|
|
269
392
|
metadata["linear_url"] = issue_data["url"]
|
|
270
|
-
|
|
393
|
+
|
|
271
394
|
if "slaBreachesAt" in issue_data and issue_data["slaBreachesAt"]:
|
|
272
395
|
metadata["sla_breaches_at"] = issue_data["slaBreachesAt"]
|
|
273
|
-
|
|
396
|
+
|
|
274
397
|
if "customerTicketCount" in issue_data:
|
|
275
398
|
metadata["customer_ticket_count"] = issue_data["customerTicketCount"]
|
|
276
|
-
|
|
399
|
+
|
|
277
400
|
return metadata
|
mcp_ticketer/adapters/linear.py
CHANGED
|
@@ -8,8 +8,8 @@ For new code, import directly from the linear package:
|
|
|
8
8
|
from mcp_ticketer.adapters.linear import LinearAdapter
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
# Import the refactored adapter
|
|
12
|
-
from .linear import LinearAdapter
|
|
11
|
+
# Import the refactored adapter from the modular structure
|
|
12
|
+
from .linear.adapter import LinearAdapter
|
|
13
13
|
|
|
14
14
|
# Re-export for backward compatibility
|
|
15
15
|
__all__ = ["LinearAdapter"]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Ticket analysis and cleanup tools for PM monitoring.
|
|
2
|
+
|
|
3
|
+
This module provides comprehensive analysis capabilities for ticket health:
|
|
4
|
+
- Similarity detection: Find duplicate or related tickets using TF-IDF
|
|
5
|
+
- Staleness detection: Identify old, inactive tickets
|
|
6
|
+
- Orphaned detection: Find tickets missing hierarchy (epic/project)
|
|
7
|
+
- Cleanup reports: Comprehensive analysis with recommendations
|
|
8
|
+
- Dependency graph: Build and analyze ticket dependency graphs
|
|
9
|
+
- Health assessment: Assess project health based on ticket metrics
|
|
10
|
+
- Project status: Comprehensive project status analysis and work planning
|
|
11
|
+
|
|
12
|
+
These tools help product managers maintain ticket health and development practices.
|
|
13
|
+
|
|
14
|
+
Note: Some analysis features require optional dependencies (scikit-learn, rapidfuzz).
|
|
15
|
+
Install with: pip install mcp-ticketer[analysis]
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
# Import dependency graph and health assessment (no optional deps required)
|
|
19
|
+
from .dependency_graph import DependencyGraph, DependencyNode
|
|
20
|
+
from .health_assessment import HealthAssessor, HealthMetrics, ProjectHealth
|
|
21
|
+
from .project_status import ProjectStatusResult, StatusAnalyzer, TicketRecommendation
|
|
22
|
+
|
|
23
|
+
# Import optional analysis modules (may fail if dependencies not installed)
|
|
24
|
+
try:
|
|
25
|
+
from .orphaned import OrphanedResult, OrphanedTicketDetector
|
|
26
|
+
from .similarity import SimilarityResult, TicketSimilarityAnalyzer
|
|
27
|
+
from .staleness import StalenessResult, StaleTicketDetector
|
|
28
|
+
|
|
29
|
+
ANALYSIS_AVAILABLE = True
|
|
30
|
+
except ImportError:
|
|
31
|
+
# Set placeholder values when optional deps not available
|
|
32
|
+
OrphanedResult = None # type: ignore
|
|
33
|
+
OrphanedTicketDetector = None # type: ignore
|
|
34
|
+
SimilarityResult = None # type: ignore
|
|
35
|
+
TicketSimilarityAnalyzer = None # type: ignore
|
|
36
|
+
StalenessResult = None # type: ignore
|
|
37
|
+
StaleTicketDetector = None # type: ignore
|
|
38
|
+
ANALYSIS_AVAILABLE = False
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"DependencyGraph",
|
|
42
|
+
"DependencyNode",
|
|
43
|
+
"HealthAssessor",
|
|
44
|
+
"HealthMetrics",
|
|
45
|
+
"ProjectHealth",
|
|
46
|
+
"ProjectStatusResult",
|
|
47
|
+
"StatusAnalyzer",
|
|
48
|
+
"TicketRecommendation",
|
|
49
|
+
"SimilarityResult",
|
|
50
|
+
"TicketSimilarityAnalyzer",
|
|
51
|
+
"StalenessResult",
|
|
52
|
+
"StaleTicketDetector",
|
|
53
|
+
"OrphanedResult",
|
|
54
|
+
"OrphanedTicketDetector",
|
|
55
|
+
"ANALYSIS_AVAILABLE",
|
|
56
|
+
]
|