mcp-ticketer 0.1.30__py3-none-any.whl → 1.2.11__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.

Potentially problematic release.


This version of mcp-ticketer might be problematic. Click here for more details.

Files changed (109) hide show
  1. mcp_ticketer/__init__.py +10 -10
  2. mcp_ticketer/__version__.py +3 -3
  3. mcp_ticketer/adapters/__init__.py +2 -0
  4. mcp_ticketer/adapters/aitrackdown.py +796 -46
  5. mcp_ticketer/adapters/asana/__init__.py +15 -0
  6. mcp_ticketer/adapters/asana/adapter.py +1416 -0
  7. mcp_ticketer/adapters/asana/client.py +292 -0
  8. mcp_ticketer/adapters/asana/mappers.py +348 -0
  9. mcp_ticketer/adapters/asana/types.py +146 -0
  10. mcp_ticketer/adapters/github.py +879 -129
  11. mcp_ticketer/adapters/hybrid.py +11 -11
  12. mcp_ticketer/adapters/jira.py +973 -73
  13. mcp_ticketer/adapters/linear/__init__.py +24 -0
  14. mcp_ticketer/adapters/linear/adapter.py +2732 -0
  15. mcp_ticketer/adapters/linear/client.py +344 -0
  16. mcp_ticketer/adapters/linear/mappers.py +420 -0
  17. mcp_ticketer/adapters/linear/queries.py +479 -0
  18. mcp_ticketer/adapters/linear/types.py +360 -0
  19. mcp_ticketer/adapters/linear.py +10 -2315
  20. mcp_ticketer/analysis/__init__.py +23 -0
  21. mcp_ticketer/analysis/orphaned.py +218 -0
  22. mcp_ticketer/analysis/similarity.py +224 -0
  23. mcp_ticketer/analysis/staleness.py +266 -0
  24. mcp_ticketer/cache/memory.py +9 -8
  25. mcp_ticketer/cli/adapter_diagnostics.py +421 -0
  26. mcp_ticketer/cli/auggie_configure.py +116 -15
  27. mcp_ticketer/cli/codex_configure.py +274 -82
  28. mcp_ticketer/cli/configure.py +888 -151
  29. mcp_ticketer/cli/diagnostics.py +400 -157
  30. mcp_ticketer/cli/discover.py +297 -26
  31. mcp_ticketer/cli/gemini_configure.py +119 -26
  32. mcp_ticketer/cli/init_command.py +880 -0
  33. mcp_ticketer/cli/instruction_commands.py +435 -0
  34. mcp_ticketer/cli/linear_commands.py +616 -0
  35. mcp_ticketer/cli/main.py +203 -1165
  36. mcp_ticketer/cli/mcp_configure.py +474 -90
  37. mcp_ticketer/cli/mcp_server_commands.py +415 -0
  38. mcp_ticketer/cli/migrate_config.py +12 -8
  39. mcp_ticketer/cli/platform_commands.py +123 -0
  40. mcp_ticketer/cli/platform_detection.py +418 -0
  41. mcp_ticketer/cli/platform_installer.py +513 -0
  42. mcp_ticketer/cli/python_detection.py +126 -0
  43. mcp_ticketer/cli/queue_commands.py +15 -15
  44. mcp_ticketer/cli/setup_command.py +639 -0
  45. mcp_ticketer/cli/simple_health.py +90 -65
  46. mcp_ticketer/cli/ticket_commands.py +1013 -0
  47. mcp_ticketer/cli/update_checker.py +313 -0
  48. mcp_ticketer/cli/utils.py +114 -66
  49. mcp_ticketer/core/__init__.py +24 -1
  50. mcp_ticketer/core/adapter.py +250 -16
  51. mcp_ticketer/core/config.py +145 -37
  52. mcp_ticketer/core/env_discovery.py +101 -22
  53. mcp_ticketer/core/env_loader.py +349 -0
  54. mcp_ticketer/core/exceptions.py +160 -0
  55. mcp_ticketer/core/http_client.py +26 -26
  56. mcp_ticketer/core/instructions.py +405 -0
  57. mcp_ticketer/core/label_manager.py +732 -0
  58. mcp_ticketer/core/mappers.py +42 -30
  59. mcp_ticketer/core/models.py +280 -28
  60. mcp_ticketer/core/onepassword_secrets.py +379 -0
  61. mcp_ticketer/core/project_config.py +183 -49
  62. mcp_ticketer/core/registry.py +3 -3
  63. mcp_ticketer/core/session_state.py +171 -0
  64. mcp_ticketer/core/state_matcher.py +592 -0
  65. mcp_ticketer/core/url_parser.py +425 -0
  66. mcp_ticketer/core/validators.py +69 -0
  67. mcp_ticketer/defaults/ticket_instructions.md +644 -0
  68. mcp_ticketer/mcp/__init__.py +29 -1
  69. mcp_ticketer/mcp/__main__.py +60 -0
  70. mcp_ticketer/mcp/server/__init__.py +25 -0
  71. mcp_ticketer/mcp/server/__main__.py +60 -0
  72. mcp_ticketer/mcp/server/constants.py +58 -0
  73. mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
  74. mcp_ticketer/mcp/server/dto.py +195 -0
  75. mcp_ticketer/mcp/server/main.py +1343 -0
  76. mcp_ticketer/mcp/server/response_builder.py +206 -0
  77. mcp_ticketer/mcp/server/routing.py +655 -0
  78. mcp_ticketer/mcp/server/server_sdk.py +151 -0
  79. mcp_ticketer/mcp/server/tools/__init__.py +56 -0
  80. mcp_ticketer/mcp/server/tools/analysis_tools.py +495 -0
  81. mcp_ticketer/mcp/server/tools/attachment_tools.py +226 -0
  82. mcp_ticketer/mcp/server/tools/bulk_tools.py +273 -0
  83. mcp_ticketer/mcp/server/tools/comment_tools.py +152 -0
  84. mcp_ticketer/mcp/server/tools/config_tools.py +1439 -0
  85. mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
  86. mcp_ticketer/mcp/server/tools/hierarchy_tools.py +921 -0
  87. mcp_ticketer/mcp/server/tools/instruction_tools.py +300 -0
  88. mcp_ticketer/mcp/server/tools/label_tools.py +948 -0
  89. mcp_ticketer/mcp/server/tools/pr_tools.py +152 -0
  90. mcp_ticketer/mcp/server/tools/search_tools.py +215 -0
  91. mcp_ticketer/mcp/server/tools/session_tools.py +170 -0
  92. mcp_ticketer/mcp/server/tools/ticket_tools.py +1268 -0
  93. mcp_ticketer/mcp/server/tools/user_ticket_tools.py +547 -0
  94. mcp_ticketer/queue/__init__.py +1 -0
  95. mcp_ticketer/queue/health_monitor.py +168 -136
  96. mcp_ticketer/queue/manager.py +95 -25
  97. mcp_ticketer/queue/queue.py +40 -21
  98. mcp_ticketer/queue/run_worker.py +6 -1
  99. mcp_ticketer/queue/ticket_registry.py +213 -155
  100. mcp_ticketer/queue/worker.py +109 -49
  101. mcp_ticketer-1.2.11.dist-info/METADATA +792 -0
  102. mcp_ticketer-1.2.11.dist-info/RECORD +110 -0
  103. mcp_ticketer/mcp/server.py +0 -1895
  104. mcp_ticketer-0.1.30.dist-info/METADATA +0 -413
  105. mcp_ticketer-0.1.30.dist-info/RECORD +0 -49
  106. {mcp_ticketer-0.1.30.dist-info → mcp_ticketer-1.2.11.dist-info}/WHEEL +0 -0
  107. {mcp_ticketer-0.1.30.dist-info → mcp_ticketer-1.2.11.dist-info}/entry_points.txt +0 -0
  108. {mcp_ticketer-0.1.30.dist-info → mcp_ticketer-1.2.11.dist-info}/licenses/LICENSE +0 -0
  109. {mcp_ticketer-0.1.30.dist-info → mcp_ticketer-1.2.11.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,360 @@
1
+ """Linear-specific types and enums."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import Enum
6
+ from typing import Any
7
+
8
+ from mcp_ticketer.core.models import Priority, TicketState
9
+
10
+
11
+ class LinearPriorityMapping:
12
+ """Mapping between universal Priority and Linear priority values."""
13
+
14
+ # Linear uses numeric priorities: 0=No priority, 1=Urgent, 2=High, 3=Medium, 4=Low
15
+ TO_LINEAR: dict[Priority, int] = {
16
+ Priority.CRITICAL: 1, # Urgent
17
+ Priority.HIGH: 2, # High
18
+ Priority.MEDIUM: 3, # Medium
19
+ Priority.LOW: 4, # Low
20
+ }
21
+
22
+ FROM_LINEAR: dict[int, Priority] = {
23
+ 0: Priority.LOW, # No priority -> Low
24
+ 1: Priority.CRITICAL, # Urgent -> Critical
25
+ 2: Priority.HIGH, # High -> High
26
+ 3: Priority.MEDIUM, # Medium -> Medium
27
+ 4: Priority.LOW, # Low -> Low
28
+ }
29
+
30
+
31
+ class LinearStateMapping:
32
+ """Mapping between universal TicketState and Linear workflow state types."""
33
+
34
+ # Linear workflow state types
35
+ TO_LINEAR: dict[TicketState, str] = {
36
+ TicketState.OPEN: "unstarted",
37
+ TicketState.IN_PROGRESS: "started",
38
+ TicketState.READY: "unstarted", # No direct equivalent, use unstarted
39
+ TicketState.TESTED: "started", # No direct equivalent, use started
40
+ TicketState.DONE: "completed",
41
+ TicketState.CLOSED: "canceled",
42
+ TicketState.WAITING: "unstarted",
43
+ TicketState.BLOCKED: "unstarted",
44
+ }
45
+
46
+ FROM_LINEAR: dict[str, TicketState] = {
47
+ "backlog": TicketState.OPEN,
48
+ "unstarted": TicketState.OPEN,
49
+ "started": TicketState.IN_PROGRESS,
50
+ "completed": TicketState.DONE,
51
+ "canceled": TicketState.CLOSED,
52
+ }
53
+
54
+
55
+ class LinearWorkflowStateType(Enum):
56
+ """Linear workflow state types."""
57
+
58
+ BACKLOG = "backlog"
59
+ UNSTARTED = "unstarted"
60
+ STARTED = "started"
61
+ COMPLETED = "completed"
62
+ CANCELED = "canceled"
63
+
64
+
65
+ class LinearProjectState(Enum):
66
+ """Linear project states."""
67
+
68
+ PLANNED = "planned"
69
+ STARTED = "started"
70
+ COMPLETED = "completed"
71
+ CANCELED = "canceled"
72
+ PAUSED = "paused"
73
+
74
+
75
+ class LinearIssueRelationType(Enum):
76
+ """Linear issue relation types."""
77
+
78
+ BLOCKS = "blocks"
79
+ BLOCKED_BY = "blockedBy"
80
+ DUPLICATE = "duplicate"
81
+ DUPLICATED_BY = "duplicatedBy"
82
+ RELATES = "relates"
83
+
84
+
85
+ class LinearCommentType(Enum):
86
+ """Linear comment types."""
87
+
88
+ COMMENT = "comment"
89
+ SYSTEM = "system"
90
+
91
+
92
+ def get_linear_priority(priority: Priority) -> int:
93
+ """Convert universal Priority to Linear priority value.
94
+
95
+ Args:
96
+ priority: Universal priority enum
97
+
98
+ Returns:
99
+ Linear priority integer (0-4)
100
+
101
+ """
102
+ return LinearPriorityMapping.TO_LINEAR.get(priority, 3) # Default to Medium
103
+
104
+
105
+ def get_universal_priority(linear_priority: int) -> Priority:
106
+ """Convert Linear priority value to universal Priority.
107
+
108
+ Args:
109
+ linear_priority: Linear priority integer (0-4)
110
+
111
+ Returns:
112
+ Universal priority enum
113
+
114
+ """
115
+ return LinearPriorityMapping.FROM_LINEAR.get(linear_priority, Priority.MEDIUM)
116
+
117
+
118
+ def get_linear_state_type(state: TicketState) -> str:
119
+ """Convert universal TicketState to Linear workflow state type.
120
+
121
+ Args:
122
+ state: Universal ticket state enum
123
+
124
+ Returns:
125
+ Linear workflow state type string
126
+
127
+ """
128
+ return LinearStateMapping.TO_LINEAR.get(state, "unstarted")
129
+
130
+
131
+ def get_universal_state(
132
+ linear_state_type: str, state_name: str | None = None
133
+ ) -> TicketState:
134
+ """Convert Linear workflow state type to universal TicketState with synonym matching.
135
+
136
+ This function implements intelligent state mapping with fallback strategies:
137
+ 1. Try exact match on state type (backlog, unstarted, started, completed, canceled)
138
+ 2. Try synonym matching on state name (ToDo, In Review, Testing, etc.)
139
+ 3. Default to OPEN for unknown states
140
+
141
+ Synonym Matching Rules (ticket 1M-164):
142
+ - "Done", "Closed", "Cancelled", "Completed", "Won't Do" → CLOSED
143
+ - Everything else → OPEN
144
+
145
+ Args:
146
+ linear_state_type: Linear workflow state type string (from state.type field)
147
+ state_name: Linear workflow state name (from state.name field, optional)
148
+
149
+ Returns:
150
+ Universal ticket state enum
151
+
152
+ """
153
+ # First try exact type match
154
+ if linear_state_type in LinearStateMapping.FROM_LINEAR:
155
+ return LinearStateMapping.FROM_LINEAR[linear_state_type]
156
+
157
+ # If no exact match and state_name provided, try synonym matching
158
+ if state_name:
159
+ state_name_lower = state_name.lower().strip()
160
+
161
+ # Check for "done/closed" synonyms - these become CLOSED
162
+ closed_synonyms = [
163
+ "done",
164
+ "closed",
165
+ "cancelled",
166
+ "canceled",
167
+ "completed",
168
+ "won't do",
169
+ "wont do",
170
+ "rejected",
171
+ "resolved",
172
+ "finished",
173
+ ]
174
+
175
+ if any(synonym in state_name_lower for synonym in closed_synonyms):
176
+ return (
177
+ TicketState.DONE if state_name_lower == "done" else TicketState.CLOSED
178
+ )
179
+
180
+ # Check for "in progress" synonyms
181
+ in_progress_synonyms = [
182
+ "in progress",
183
+ "in-progress",
184
+ "working",
185
+ "active",
186
+ "started",
187
+ "doing",
188
+ "in development",
189
+ "in dev",
190
+ ]
191
+
192
+ if any(synonym in state_name_lower for synonym in in_progress_synonyms):
193
+ return TicketState.IN_PROGRESS
194
+
195
+ # Check for "review/testing" synonyms
196
+ review_synonyms = [
197
+ "review",
198
+ "in review",
199
+ "in-review",
200
+ "testing",
201
+ "in test",
202
+ "in-test",
203
+ "qa",
204
+ "ready for review",
205
+ ]
206
+
207
+ if any(synonym in state_name_lower for synonym in review_synonyms):
208
+ return TicketState.READY
209
+
210
+ # Default: everything else is OPEN (including "ToDo", "Backlog", "To Do", etc.)
211
+ return TicketState.OPEN
212
+
213
+
214
+ def build_issue_filter(
215
+ state: TicketState | None = None,
216
+ assignee_id: str | None = None,
217
+ priority: Priority | None = None,
218
+ team_id: str | None = None,
219
+ project_id: str | None = None,
220
+ parent_id: str | None = None,
221
+ labels: list[str] | None = None,
222
+ created_after: str | None = None,
223
+ updated_after: str | None = None,
224
+ due_before: str | None = None,
225
+ include_archived: bool = False,
226
+ ) -> dict[str, Any]:
227
+ """Build a Linear issue filter from parameters.
228
+
229
+ Args:
230
+ state: Filter by ticket state
231
+ assignee_id: Filter by assignee Linear user ID
232
+ priority: Filter by priority
233
+ team_id: Filter by team ID
234
+ project_id: Filter by project ID
235
+ parent_id: Filter by parent issue ID (for listing sub-issues)
236
+ labels: Filter by label names
237
+ created_after: Filter by creation date (ISO string)
238
+ updated_after: Filter by update date (ISO string)
239
+ due_before: Filter by due date (ISO string)
240
+ include_archived: Whether to include archived issues
241
+
242
+ Returns:
243
+ Linear GraphQL filter object
244
+
245
+ """
246
+ issue_filter: dict[str, Any] = {}
247
+
248
+ # Team filter (required for most operations)
249
+ if team_id:
250
+ issue_filter["team"] = {"id": {"eq": team_id}}
251
+
252
+ # State filter
253
+ if state:
254
+ state_type = get_linear_state_type(state)
255
+ issue_filter["state"] = {"type": {"eq": state_type}}
256
+
257
+ # Assignee filter
258
+ if assignee_id:
259
+ issue_filter["assignee"] = {"id": {"eq": assignee_id}}
260
+
261
+ # Priority filter
262
+ if priority:
263
+ linear_priority = get_linear_priority(priority)
264
+ issue_filter["priority"] = {"eq": linear_priority}
265
+
266
+ # Project filter
267
+ if project_id:
268
+ issue_filter["project"] = {"id": {"eq": project_id}}
269
+
270
+ # Parent filter (for listing children/sub-issues)
271
+ if parent_id:
272
+ issue_filter["parent"] = {"id": {"eq": parent_id}}
273
+
274
+ # Labels filter
275
+ if labels:
276
+ issue_filter["labels"] = {"some": {"name": {"in": labels}}}
277
+
278
+ # Date filters
279
+ if created_after:
280
+ issue_filter["createdAt"] = {"gte": created_after}
281
+ if updated_after:
282
+ issue_filter["updatedAt"] = {"gte": updated_after}
283
+ if due_before:
284
+ issue_filter["dueDate"] = {"lte": due_before}
285
+
286
+ # Archived filter
287
+ if not include_archived:
288
+ issue_filter["archivedAt"] = {"null": True}
289
+
290
+ return issue_filter
291
+
292
+
293
+ def build_project_filter(
294
+ state: str | None = None,
295
+ team_id: str | None = None,
296
+ include_completed: bool = True,
297
+ ) -> dict[str, Any]:
298
+ """Build a Linear project filter from parameters.
299
+
300
+ Args:
301
+ state: Filter by project state
302
+ team_id: Filter by team ID
303
+ include_completed: Whether to include completed projects
304
+
305
+ Returns:
306
+ Linear GraphQL filter object
307
+
308
+ """
309
+ project_filter: dict[str, Any] = {}
310
+
311
+ # Team filter
312
+ if team_id:
313
+ project_filter["teams"] = {"some": {"id": {"eq": team_id}}}
314
+
315
+ # State filter
316
+ if state:
317
+ project_filter["state"] = {"eq": state}
318
+ elif not include_completed:
319
+ # Exclude completed projects by default
320
+ project_filter["state"] = {"neq": "completed"}
321
+
322
+ return project_filter
323
+
324
+
325
+ def extract_linear_metadata(issue_data: dict[str, Any]) -> dict[str, Any]:
326
+ """Extract Linear-specific metadata from issue data.
327
+
328
+ Args:
329
+ issue_data: Raw Linear issue data from GraphQL
330
+
331
+ Returns:
332
+ Dictionary of Linear-specific metadata
333
+
334
+ """
335
+ metadata = {}
336
+
337
+ # Extract Linear-specific fields
338
+ if "dueDate" in issue_data and issue_data["dueDate"]:
339
+ metadata["due_date"] = issue_data["dueDate"]
340
+
341
+ if "cycle" in issue_data and issue_data["cycle"]:
342
+ metadata["cycle_id"] = issue_data["cycle"]["id"]
343
+ metadata["cycle_name"] = issue_data["cycle"]["name"]
344
+
345
+ if "estimate" in issue_data and issue_data["estimate"]:
346
+ metadata["estimate"] = issue_data["estimate"]
347
+
348
+ if "branchName" in issue_data and issue_data["branchName"]:
349
+ metadata["branch_name"] = issue_data["branchName"]
350
+
351
+ if "url" in issue_data:
352
+ metadata["linear_url"] = issue_data["url"]
353
+
354
+ if "slaBreachesAt" in issue_data and issue_data["slaBreachesAt"]:
355
+ metadata["sla_breaches_at"] = issue_data["slaBreachesAt"]
356
+
357
+ if "customerTicketCount" in issue_data:
358
+ metadata["customer_ticket_count"] = issue_data["customerTicketCount"]
359
+
360
+ return metadata