mcp-ticketer 0.12.0__py3-none-any.whl → 2.2.13__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 (129) hide show
  1. mcp_ticketer/__init__.py +10 -10
  2. mcp_ticketer/__version__.py +1 -1
  3. mcp_ticketer/_version_scm.py +1 -0
  4. mcp_ticketer/adapters/aitrackdown.py +507 -6
  5. mcp_ticketer/adapters/asana/adapter.py +229 -0
  6. mcp_ticketer/adapters/asana/mappers.py +14 -0
  7. mcp_ticketer/adapters/github/__init__.py +26 -0
  8. mcp_ticketer/adapters/github/adapter.py +3229 -0
  9. mcp_ticketer/adapters/github/client.py +335 -0
  10. mcp_ticketer/adapters/github/mappers.py +797 -0
  11. mcp_ticketer/adapters/github/queries.py +692 -0
  12. mcp_ticketer/adapters/github/types.py +460 -0
  13. mcp_ticketer/adapters/hybrid.py +47 -5
  14. mcp_ticketer/adapters/jira/__init__.py +35 -0
  15. mcp_ticketer/adapters/jira/adapter.py +1351 -0
  16. mcp_ticketer/adapters/jira/client.py +271 -0
  17. mcp_ticketer/adapters/jira/mappers.py +246 -0
  18. mcp_ticketer/adapters/jira/queries.py +216 -0
  19. mcp_ticketer/adapters/jira/types.py +304 -0
  20. mcp_ticketer/adapters/linear/adapter.py +2730 -139
  21. mcp_ticketer/adapters/linear/client.py +175 -3
  22. mcp_ticketer/adapters/linear/mappers.py +203 -8
  23. mcp_ticketer/adapters/linear/queries.py +280 -3
  24. mcp_ticketer/adapters/linear/types.py +120 -4
  25. mcp_ticketer/analysis/__init__.py +56 -0
  26. mcp_ticketer/analysis/dependency_graph.py +255 -0
  27. mcp_ticketer/analysis/health_assessment.py +304 -0
  28. mcp_ticketer/analysis/orphaned.py +218 -0
  29. mcp_ticketer/analysis/project_status.py +594 -0
  30. mcp_ticketer/analysis/similarity.py +224 -0
  31. mcp_ticketer/analysis/staleness.py +266 -0
  32. mcp_ticketer/automation/__init__.py +11 -0
  33. mcp_ticketer/automation/project_updates.py +378 -0
  34. mcp_ticketer/cli/adapter_diagnostics.py +3 -1
  35. mcp_ticketer/cli/auggie_configure.py +17 -5
  36. mcp_ticketer/cli/codex_configure.py +97 -61
  37. mcp_ticketer/cli/configure.py +1288 -105
  38. mcp_ticketer/cli/cursor_configure.py +314 -0
  39. mcp_ticketer/cli/diagnostics.py +13 -12
  40. mcp_ticketer/cli/discover.py +5 -0
  41. mcp_ticketer/cli/gemini_configure.py +17 -5
  42. mcp_ticketer/cli/init_command.py +880 -0
  43. mcp_ticketer/cli/install_mcp_server.py +418 -0
  44. mcp_ticketer/cli/instruction_commands.py +6 -0
  45. mcp_ticketer/cli/main.py +267 -3175
  46. mcp_ticketer/cli/mcp_configure.py +821 -119
  47. mcp_ticketer/cli/mcp_server_commands.py +415 -0
  48. mcp_ticketer/cli/platform_detection.py +77 -12
  49. mcp_ticketer/cli/platform_installer.py +545 -0
  50. mcp_ticketer/cli/project_update_commands.py +350 -0
  51. mcp_ticketer/cli/setup_command.py +795 -0
  52. mcp_ticketer/cli/simple_health.py +12 -10
  53. mcp_ticketer/cli/ticket_commands.py +705 -103
  54. mcp_ticketer/cli/utils.py +113 -0
  55. mcp_ticketer/core/__init__.py +56 -6
  56. mcp_ticketer/core/adapter.py +533 -2
  57. mcp_ticketer/core/config.py +21 -21
  58. mcp_ticketer/core/exceptions.py +7 -1
  59. mcp_ticketer/core/label_manager.py +732 -0
  60. mcp_ticketer/core/mappers.py +31 -19
  61. mcp_ticketer/core/milestone_manager.py +252 -0
  62. mcp_ticketer/core/models.py +480 -0
  63. mcp_ticketer/core/onepassword_secrets.py +1 -1
  64. mcp_ticketer/core/priority_matcher.py +463 -0
  65. mcp_ticketer/core/project_config.py +132 -14
  66. mcp_ticketer/core/project_utils.py +281 -0
  67. mcp_ticketer/core/project_validator.py +376 -0
  68. mcp_ticketer/core/session_state.py +176 -0
  69. mcp_ticketer/core/state_matcher.py +625 -0
  70. mcp_ticketer/core/url_parser.py +425 -0
  71. mcp_ticketer/core/validators.py +69 -0
  72. mcp_ticketer/mcp/server/__main__.py +2 -1
  73. mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
  74. mcp_ticketer/mcp/server/main.py +106 -25
  75. mcp_ticketer/mcp/server/routing.py +723 -0
  76. mcp_ticketer/mcp/server/server_sdk.py +58 -0
  77. mcp_ticketer/mcp/server/tools/__init__.py +33 -11
  78. mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
  79. mcp_ticketer/mcp/server/tools/attachment_tools.py +5 -5
  80. mcp_ticketer/mcp/server/tools/bulk_tools.py +259 -202
  81. mcp_ticketer/mcp/server/tools/comment_tools.py +74 -12
  82. mcp_ticketer/mcp/server/tools/config_tools.py +1391 -145
  83. mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
  84. mcp_ticketer/mcp/server/tools/hierarchy_tools.py +870 -460
  85. mcp_ticketer/mcp/server/tools/instruction_tools.py +7 -5
  86. mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
  87. mcp_ticketer/mcp/server/tools/milestone_tools.py +338 -0
  88. mcp_ticketer/mcp/server/tools/pr_tools.py +3 -7
  89. mcp_ticketer/mcp/server/tools/project_status_tools.py +158 -0
  90. mcp_ticketer/mcp/server/tools/project_update_tools.py +473 -0
  91. mcp_ticketer/mcp/server/tools/search_tools.py +209 -97
  92. mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
  93. mcp_ticketer/mcp/server/tools/ticket_tools.py +1107 -124
  94. mcp_ticketer/mcp/server/tools/user_ticket_tools.py +218 -236
  95. mcp_ticketer/queue/queue.py +68 -0
  96. mcp_ticketer/queue/worker.py +1 -1
  97. mcp_ticketer/utils/__init__.py +5 -0
  98. mcp_ticketer/utils/token_utils.py +246 -0
  99. mcp_ticketer-2.2.13.dist-info/METADATA +1396 -0
  100. mcp_ticketer-2.2.13.dist-info/RECORD +158 -0
  101. mcp_ticketer-2.2.13.dist-info/top_level.txt +2 -0
  102. py_mcp_installer/examples/phase3_demo.py +178 -0
  103. py_mcp_installer/scripts/manage_version.py +54 -0
  104. py_mcp_installer/setup.py +6 -0
  105. py_mcp_installer/src/py_mcp_installer/__init__.py +153 -0
  106. py_mcp_installer/src/py_mcp_installer/command_builder.py +445 -0
  107. py_mcp_installer/src/py_mcp_installer/config_manager.py +541 -0
  108. py_mcp_installer/src/py_mcp_installer/exceptions.py +243 -0
  109. py_mcp_installer/src/py_mcp_installer/installation_strategy.py +617 -0
  110. py_mcp_installer/src/py_mcp_installer/installer.py +656 -0
  111. py_mcp_installer/src/py_mcp_installer/mcp_inspector.py +750 -0
  112. py_mcp_installer/src/py_mcp_installer/platform_detector.py +451 -0
  113. py_mcp_installer/src/py_mcp_installer/platforms/__init__.py +26 -0
  114. py_mcp_installer/src/py_mcp_installer/platforms/claude_code.py +225 -0
  115. py_mcp_installer/src/py_mcp_installer/platforms/codex.py +181 -0
  116. py_mcp_installer/src/py_mcp_installer/platforms/cursor.py +191 -0
  117. py_mcp_installer/src/py_mcp_installer/types.py +222 -0
  118. py_mcp_installer/src/py_mcp_installer/utils.py +463 -0
  119. py_mcp_installer/tests/__init__.py +0 -0
  120. py_mcp_installer/tests/platforms/__init__.py +0 -0
  121. py_mcp_installer/tests/test_platform_detector.py +17 -0
  122. mcp_ticketer/adapters/github.py +0 -1574
  123. mcp_ticketer/adapters/jira.py +0 -1258
  124. mcp_ticketer-0.12.0.dist-info/METADATA +0 -550
  125. mcp_ticketer-0.12.0.dist-info/RECORD +0 -91
  126. mcp_ticketer-0.12.0.dist-info/top_level.txt +0 -1
  127. {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/WHEEL +0 -0
  128. {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/entry_points.txt +0 -0
  129. {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,350 @@
1
+ """CLI commands for project updates (Linear project status updates)."""
2
+
3
+ import asyncio
4
+ from datetime import datetime
5
+
6
+ import typer
7
+ from rich.console import Console
8
+ from rich.panel import Panel
9
+ from rich.table import Table
10
+
11
+ from ..core.models import ProjectUpdateHealth
12
+
13
+ app = typer.Typer(name="project-update", help="Project status update management")
14
+ console = Console()
15
+
16
+
17
+ def _format_health_indicator(health: ProjectUpdateHealth | str | None) -> str:
18
+ """Format health status with color-coded indicators.
19
+
20
+ Args:
21
+ health: Health status enum or string value
22
+
23
+ Returns:
24
+ Formatted health indicator with color and icon
25
+ """
26
+ if not health:
27
+ return "[dim]○ Not Set[/dim]"
28
+
29
+ # Handle both enum and string values
30
+ health_str = health.value if isinstance(health, ProjectUpdateHealth) else health
31
+
32
+ health_indicators = {
33
+ "on_track": "[green]✓ On Track[/green]",
34
+ "at_risk": "[yellow]⚠ At Risk[/yellow]",
35
+ "off_track": "[red]✗ Off Track[/red]",
36
+ "complete": "[blue]✓ Complete[/blue]",
37
+ "inactive": "[dim]○ Inactive[/dim]",
38
+ }
39
+
40
+ return health_indicators.get(health_str, f"[dim]{health_str}[/dim]")
41
+
42
+
43
+ def _format_relative_time(dt: datetime) -> str:
44
+ """Format datetime as relative time string.
45
+
46
+ Args:
47
+ dt: Datetime to format
48
+
49
+ Returns:
50
+ Human-readable relative time (e.g., "2 hours ago")
51
+ """
52
+ try:
53
+ from humanize import naturaltime
54
+
55
+ return naturaltime(dt)
56
+ except ImportError:
57
+ # Fallback to ISO format if humanize not available
58
+ return dt.strftime("%Y-%m-%d %H:%M:%S")
59
+
60
+
61
+ async def _create_update_async(
62
+ project_id: str,
63
+ body: str,
64
+ health: ProjectUpdateHealth | None = None,
65
+ ) -> None:
66
+ """Async implementation of create command."""
67
+ from .main import get_adapter
68
+
69
+ try:
70
+ adapter = get_adapter()
71
+
72
+ # Check if adapter supports project updates
73
+ if not hasattr(adapter, "create_project_update"):
74
+ adapter_name = getattr(adapter, "adapter_name", type(adapter).__name__)
75
+ console.print(
76
+ f"[red]✗[/red] Adapter '{adapter_name}' does not support project updates"
77
+ )
78
+ console.print(
79
+ "[dim]Project updates are supported by: Linear, GitHub V2, Asana[/dim]"
80
+ )
81
+ raise typer.Exit(1) from None
82
+
83
+ # Validate body is not empty
84
+ if not body or not body.strip():
85
+ console.print("[red]✗[/red] Update body cannot be empty")
86
+ raise typer.Exit(1) from None
87
+
88
+ # Create the project update
89
+ console.print(f"[dim]Creating project update for '{project_id}'...[/dim]")
90
+ update = await adapter.create_project_update(
91
+ project_id=project_id,
92
+ body=body,
93
+ health=health,
94
+ )
95
+
96
+ # Display success message with update details
97
+ console.print("\n[green]✓[/green] Project update created successfully!")
98
+ console.print(f" Update ID: [cyan]{update.id}[/cyan]")
99
+ console.print(
100
+ f" Project: [bold]{update.project_name or update.project_id}[/bold]"
101
+ )
102
+ console.print(f" Health: {_format_health_indicator(update.health)}")
103
+ console.print(f" Created: {update.created_at.strftime('%Y-%m-%d %H:%M:%S')}")
104
+
105
+ if update.url:
106
+ console.print(f" URL: [link]{update.url}[/link]")
107
+
108
+ # Show preview of body
109
+ preview = body[:100] + "..." if len(body) > 100 else body
110
+ console.print(f" Body Preview: {preview}")
111
+
112
+ except ValueError as e:
113
+ console.print(f"[red]✗[/red] {str(e)}")
114
+ raise typer.Exit(1) from e
115
+ except Exception as e:
116
+ console.print(f"[red]✗[/red] Unexpected error: {str(e)}")
117
+ raise typer.Exit(1) from e
118
+
119
+
120
+ async def _list_updates_async(
121
+ project_id: str,
122
+ limit: int = 10,
123
+ ) -> None:
124
+ """Async implementation of list command."""
125
+ from .main import get_adapter
126
+
127
+ try:
128
+ adapter = get_adapter()
129
+
130
+ # Check if adapter supports project updates
131
+ if not hasattr(adapter, "list_project_updates"):
132
+ adapter_name = getattr(adapter, "adapter_name", type(adapter).__name__)
133
+ console.print(
134
+ f"[red]✗[/red] Adapter '{adapter_name}' does not support project updates"
135
+ )
136
+ raise typer.Exit(1) from None
137
+
138
+ # List project updates
139
+ console.print(f"[dim]Fetching updates for project '{project_id}'...[/dim]")
140
+ updates = await adapter.list_project_updates(
141
+ project_id=project_id,
142
+ limit=limit,
143
+ )
144
+
145
+ if not updates:
146
+ console.print(
147
+ f"\n[yellow]No updates found for project '{project_id}'[/yellow]"
148
+ )
149
+ return
150
+
151
+ # Create Rich table
152
+ table = Table(title=f"Project Updates ({len(updates)} total)")
153
+ table.add_column("ID", style="cyan", no_wrap=True)
154
+ table.add_column("Date", style="dim")
155
+ table.add_column("Health", no_wrap=True)
156
+ table.add_column("Author", style="blue")
157
+ table.add_column("Preview", style="white")
158
+
159
+ for update in updates:
160
+ # Format date
161
+ date_str = (
162
+ _format_relative_time(update.created_at)
163
+ if update.created_at
164
+ else "Unknown"
165
+ )
166
+
167
+ # Format health
168
+ health_str = _format_health_indicator(update.health)
169
+
170
+ # Format author
171
+ author = update.author_name or update.author_id or "Unknown"
172
+
173
+ # Preview (first 50 chars of body)
174
+ preview = update.body[:50] + "..." if len(update.body) > 50 else update.body
175
+
176
+ table.add_row(
177
+ update.id[:8] + "..." if len(update.id) > 8 else update.id,
178
+ date_str,
179
+ health_str,
180
+ author,
181
+ preview,
182
+ )
183
+
184
+ console.print(table)
185
+
186
+ except ValueError as e:
187
+ console.print(f"[red]✗[/red] {str(e)}")
188
+ raise typer.Exit(1) from e
189
+ except Exception as e:
190
+ console.print(f"[red]✗[/red] Unexpected error: {str(e)}")
191
+ raise typer.Exit(1) from e
192
+
193
+
194
+ async def _get_update_async(update_id: str) -> None:
195
+ """Async implementation of get command."""
196
+ from .main import get_adapter
197
+
198
+ try:
199
+ adapter = get_adapter()
200
+
201
+ # Check if adapter supports project updates
202
+ if not hasattr(adapter, "get_project_update"):
203
+ adapter_name = getattr(adapter, "adapter_name", type(adapter).__name__)
204
+ console.print(
205
+ f"[red]✗[/red] Adapter '{adapter_name}' does not support project updates"
206
+ )
207
+ raise typer.Exit(1) from None
208
+
209
+ # Get project update
210
+ console.print(f"[dim]Fetching update '{update_id}'...[/dim]")
211
+ update = await adapter.get_project_update(update_id=update_id)
212
+
213
+ # Build detailed display content
214
+ content_lines = [
215
+ f"[bold]Update ID:[/bold] {update.id}",
216
+ f"[bold]Project:[/bold] {update.project_name or update.project_id}",
217
+ f"[bold]Health:[/bold] {_format_health_indicator(update.health)}",
218
+ f"[bold]Created:[/bold] {update.created_at.strftime('%Y-%m-%d %H:%M:%S')}",
219
+ ]
220
+
221
+ if update.author_name or update.author_id:
222
+ author = update.author_name or update.author_id
223
+ content_lines.append(f"[bold]Author:[/bold] {author}")
224
+
225
+ if update.url:
226
+ content_lines.append(f"[bold]URL:[/bold] [link]{update.url}[/link]")
227
+
228
+ # Add body
229
+ content_lines.append("")
230
+ content_lines.append("[bold]Body:[/bold]")
231
+ content_lines.append(update.body)
232
+
233
+ # Add Linear-specific fields if present
234
+ if update.diff_markdown:
235
+ content_lines.append("")
236
+ content_lines.append("[bold]Diff (Changes Since Last Update):[/bold]")
237
+ content_lines.append(update.diff_markdown)
238
+
239
+ if update.is_stale is not None:
240
+ stale_indicator = (
241
+ "[red]⚠ Stale[/red]" if update.is_stale else "[green]✓ Current[/green]"
242
+ )
243
+ content_lines.append("")
244
+ content_lines.append(f"[bold]Freshness:[/bold] {stale_indicator}")
245
+
246
+ # Display as Rich panel
247
+ panel = Panel(
248
+ "\n".join(content_lines),
249
+ title="[bold]Project Update Details[/bold]",
250
+ border_style="cyan",
251
+ expand=False,
252
+ )
253
+
254
+ console.print(panel)
255
+
256
+ except ValueError as e:
257
+ console.print(f"[red]✗[/red] {str(e)}")
258
+ raise typer.Exit(1) from e
259
+ except Exception as e:
260
+ console.print(f"[red]✗[/red] Unexpected error: {str(e)}")
261
+ raise typer.Exit(1) from e
262
+
263
+
264
+ @app.command("create")
265
+ def create_update(
266
+ project_id: str = typer.Argument(
267
+ ...,
268
+ help="Project identifier (UUID, slugId, short ID, or URL)",
269
+ ),
270
+ body: str = typer.Argument(
271
+ ...,
272
+ help="Update content (markdown formatted)",
273
+ ),
274
+ health: ProjectUpdateHealth | None = typer.Option(
275
+ None,
276
+ "--health",
277
+ "-h",
278
+ help="Project health status (on_track, at_risk, off_track, complete, inactive)",
279
+ ),
280
+ ) -> None:
281
+ """Create a new project status update.
282
+
283
+ Creates a status update for a project with optional health indicator.
284
+ Linear projects will automatically generate a diff showing changes.
285
+
286
+ Examples:
287
+ # Create update with health status
288
+ mcp-ticket project-update create "PROJ-123" "Sprint completed successfully" --health on_track
289
+
290
+ # Create update using project URL
291
+ mcp-ticket project-update create "https://linear.app/team/project/proj-slug" "Weekly update"
292
+
293
+ # Create update without health indicator
294
+ mcp-ticket project-update create "mcp-ticketer-eac28953c267" "Development ongoing"
295
+ """
296
+ asyncio.run(_create_update_async(project_id, body, health))
297
+
298
+
299
+ @app.command("list")
300
+ def list_updates(
301
+ project_id: str = typer.Argument(
302
+ ...,
303
+ help="Project identifier (UUID, slugId, short ID, or URL)",
304
+ ),
305
+ limit: int = typer.Option(
306
+ 10,
307
+ "--limit",
308
+ "-l",
309
+ help="Maximum number of updates to return",
310
+ min=1,
311
+ max=100,
312
+ ),
313
+ ) -> None:
314
+ """List recent project updates.
315
+
316
+ Retrieves recent status updates for a project, ordered by creation date (newest first).
317
+
318
+ Examples:
319
+ # List last 10 updates (default)
320
+ mcp-ticket project-update list "PROJ-123"
321
+
322
+ # List last 20 updates
323
+ mcp-ticket project-update list "PROJ-123" --limit 20
324
+
325
+ # List updates using project URL
326
+ mcp-ticket project-update list "https://linear.app/team/project/proj-slug"
327
+ """
328
+ asyncio.run(_list_updates_async(project_id, limit))
329
+
330
+
331
+ @app.command("get")
332
+ def get_update(
333
+ update_id: str = typer.Argument(
334
+ ...,
335
+ help="Project update identifier (UUID)",
336
+ ),
337
+ ) -> None:
338
+ """Get detailed information about a specific project update.
339
+
340
+ Retrieves full details including body, health status, author, and Linear-specific
341
+ fields like auto-generated diffs and staleness indicators.
342
+
343
+ Examples:
344
+ # Get update details
345
+ mcp-ticket project-update get "update-uuid-here"
346
+
347
+ # View update with full diff
348
+ mcp-ticket project-update get "abc123def456"
349
+ """
350
+ asyncio.run(_get_update_async(update_id))