mcp-ticketer 0.4.11__py3-none-any.whl → 2.0.1__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 (111) 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 +394 -9
  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 +836 -105
  11. mcp_ticketer/adapters/hybrid.py +47 -5
  12. mcp_ticketer/adapters/jira.py +772 -1
  13. mcp_ticketer/adapters/linear/adapter.py +2293 -108
  14. mcp_ticketer/adapters/linear/client.py +146 -12
  15. mcp_ticketer/adapters/linear/mappers.py +105 -11
  16. mcp_ticketer/adapters/linear/queries.py +168 -1
  17. mcp_ticketer/adapters/linear/types.py +80 -4
  18. mcp_ticketer/analysis/__init__.py +56 -0
  19. mcp_ticketer/analysis/dependency_graph.py +255 -0
  20. mcp_ticketer/analysis/health_assessment.py +304 -0
  21. mcp_ticketer/analysis/orphaned.py +218 -0
  22. mcp_ticketer/analysis/project_status.py +594 -0
  23. mcp_ticketer/analysis/similarity.py +224 -0
  24. mcp_ticketer/analysis/staleness.py +266 -0
  25. mcp_ticketer/automation/__init__.py +11 -0
  26. mcp_ticketer/automation/project_updates.py +378 -0
  27. mcp_ticketer/cache/memory.py +3 -3
  28. mcp_ticketer/cli/adapter_diagnostics.py +4 -2
  29. mcp_ticketer/cli/auggie_configure.py +18 -6
  30. mcp_ticketer/cli/codex_configure.py +175 -60
  31. mcp_ticketer/cli/configure.py +884 -146
  32. mcp_ticketer/cli/cursor_configure.py +314 -0
  33. mcp_ticketer/cli/diagnostics.py +31 -28
  34. mcp_ticketer/cli/discover.py +293 -21
  35. mcp_ticketer/cli/gemini_configure.py +18 -6
  36. mcp_ticketer/cli/init_command.py +880 -0
  37. mcp_ticketer/cli/instruction_commands.py +435 -0
  38. mcp_ticketer/cli/linear_commands.py +99 -15
  39. mcp_ticketer/cli/main.py +109 -2055
  40. mcp_ticketer/cli/mcp_configure.py +673 -99
  41. mcp_ticketer/cli/mcp_server_commands.py +415 -0
  42. mcp_ticketer/cli/migrate_config.py +12 -8
  43. mcp_ticketer/cli/platform_commands.py +6 -6
  44. mcp_ticketer/cli/platform_detection.py +477 -0
  45. mcp_ticketer/cli/platform_installer.py +536 -0
  46. mcp_ticketer/cli/project_update_commands.py +350 -0
  47. mcp_ticketer/cli/queue_commands.py +15 -15
  48. mcp_ticketer/cli/setup_command.py +639 -0
  49. mcp_ticketer/cli/simple_health.py +13 -11
  50. mcp_ticketer/cli/ticket_commands.py +277 -36
  51. mcp_ticketer/cli/update_checker.py +313 -0
  52. mcp_ticketer/cli/utils.py +45 -41
  53. mcp_ticketer/core/__init__.py +35 -1
  54. mcp_ticketer/core/adapter.py +170 -5
  55. mcp_ticketer/core/config.py +38 -31
  56. mcp_ticketer/core/env_discovery.py +33 -3
  57. mcp_ticketer/core/env_loader.py +7 -6
  58. mcp_ticketer/core/exceptions.py +10 -4
  59. mcp_ticketer/core/http_client.py +10 -10
  60. mcp_ticketer/core/instructions.py +405 -0
  61. mcp_ticketer/core/label_manager.py +732 -0
  62. mcp_ticketer/core/mappers.py +32 -20
  63. mcp_ticketer/core/models.py +136 -1
  64. mcp_ticketer/core/onepassword_secrets.py +379 -0
  65. mcp_ticketer/core/priority_matcher.py +463 -0
  66. mcp_ticketer/core/project_config.py +148 -14
  67. mcp_ticketer/core/registry.py +1 -1
  68. mcp_ticketer/core/session_state.py +171 -0
  69. mcp_ticketer/core/state_matcher.py +592 -0
  70. mcp_ticketer/core/url_parser.py +425 -0
  71. mcp_ticketer/core/validators.py +69 -0
  72. mcp_ticketer/defaults/ticket_instructions.md +644 -0
  73. mcp_ticketer/mcp/__init__.py +2 -2
  74. mcp_ticketer/mcp/server/__init__.py +2 -2
  75. mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
  76. mcp_ticketer/mcp/server/main.py +187 -93
  77. mcp_ticketer/mcp/server/routing.py +655 -0
  78. mcp_ticketer/mcp/server/server_sdk.py +58 -0
  79. mcp_ticketer/mcp/server/tools/__init__.py +37 -9
  80. mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
  81. mcp_ticketer/mcp/server/tools/attachment_tools.py +65 -20
  82. mcp_ticketer/mcp/server/tools/bulk_tools.py +259 -202
  83. mcp_ticketer/mcp/server/tools/comment_tools.py +74 -12
  84. mcp_ticketer/mcp/server/tools/config_tools.py +1429 -0
  85. mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
  86. mcp_ticketer/mcp/server/tools/hierarchy_tools.py +878 -319
  87. mcp_ticketer/mcp/server/tools/instruction_tools.py +295 -0
  88. mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
  89. mcp_ticketer/mcp/server/tools/pr_tools.py +3 -7
  90. mcp_ticketer/mcp/server/tools/project_status_tools.py +158 -0
  91. mcp_ticketer/mcp/server/tools/project_update_tools.py +473 -0
  92. mcp_ticketer/mcp/server/tools/search_tools.py +180 -97
  93. mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
  94. mcp_ticketer/mcp/server/tools/ticket_tools.py +1182 -82
  95. mcp_ticketer/mcp/server/tools/user_ticket_tools.py +364 -0
  96. mcp_ticketer/queue/health_monitor.py +1 -0
  97. mcp_ticketer/queue/manager.py +4 -4
  98. mcp_ticketer/queue/queue.py +3 -3
  99. mcp_ticketer/queue/run_worker.py +1 -1
  100. mcp_ticketer/queue/ticket_registry.py +2 -2
  101. mcp_ticketer/queue/worker.py +15 -13
  102. mcp_ticketer/utils/__init__.py +5 -0
  103. mcp_ticketer/utils/token_utils.py +246 -0
  104. mcp_ticketer-2.0.1.dist-info/METADATA +1366 -0
  105. mcp_ticketer-2.0.1.dist-info/RECORD +122 -0
  106. mcp_ticketer-0.4.11.dist-info/METADATA +0 -496
  107. mcp_ticketer-0.4.11.dist-info/RECORD +0 -77
  108. {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/WHEEL +0 -0
  109. {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/entry_points.txt +0 -0
  110. {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/licenses/LICENSE +0 -0
  111. {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,415 @@
1
+ """MCP server management commands for mcp-ticketer."""
2
+
3
+ import json
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ import typer
8
+ from rich.console import Console
9
+
10
+ console = Console()
11
+
12
+ # Create MCP configuration command group
13
+ mcp_app = typer.Typer(
14
+ name="mcp",
15
+ help="Configure MCP integration for AI clients (Claude, Gemini, Codex, Auggie)",
16
+ add_completion=False,
17
+ invoke_without_command=True,
18
+ )
19
+
20
+
21
+ @mcp_app.callback()
22
+ def mcp_callback(
23
+ ctx: typer.Context,
24
+ project_path: str | None = typer.Option(
25
+ None, "--path", "-p", help="Project directory path (default: current directory)"
26
+ ),
27
+ ) -> None:
28
+ """MCP command group - runs MCP server if no subcommand provided.
29
+
30
+ Examples:
31
+ --------
32
+ mcp-ticketer mcp # Start server in current directory
33
+ mcp-ticketer mcp --path /dir # Start server in specific directory
34
+ mcp-ticketer mcp -p /dir # Start server (short form)
35
+ mcp-ticketer mcp status # Check MCP status
36
+ mcp-ticketer mcp serve # Explicitly start server
37
+
38
+ """
39
+ if ctx.invoked_subcommand is None:
40
+ # No subcommand provided, run the serve command
41
+ # Change to project directory if provided
42
+ if project_path:
43
+ import os
44
+
45
+ os.chdir(project_path)
46
+ # Invoke the serve command through context
47
+ ctx.invoke(mcp_serve, adapter=None, base_path=None)
48
+
49
+
50
+ @mcp_app.command(name="serve")
51
+ def mcp_serve(
52
+ adapter: str | None = typer.Option(
53
+ None, "--adapter", "-a", help="Override default adapter type"
54
+ ),
55
+ base_path: str | None = typer.Option(
56
+ None, "--base-path", help="Base path for AITrackdown adapter"
57
+ ),
58
+ ) -> None:
59
+ """Start MCP server for JSON-RPC communication over stdio.
60
+
61
+ This command is used by Claude Code/Desktop when connecting to the MCP server.
62
+ You typically don't need to run this manually - use 'mcp-ticketer install add' to configure.
63
+
64
+ Configuration Resolution:
65
+ - When MCP server starts, it uses the current working directory (cwd)
66
+ - The cwd is set by Claude Code/Desktop from the 'cwd' field in .mcp/config.json
67
+ - Configuration is loaded with this priority:
68
+ 1. Project-specific: .mcp-ticketer/config.json in cwd
69
+ 2. Global: ~/.mcp-ticketer/config.json
70
+ 3. Default: aitrackdown adapter with .aitrackdown base path
71
+ """
72
+ # Local imports to avoid circular dependency
73
+ from ..mcp.server.server_sdk import configure_adapter
74
+ from ..mcp.server.server_sdk import main as sdk_main
75
+
76
+ # Import load_config locally to avoid circular import
77
+ # (main.py imports this module, so we can't import from main at module level)
78
+ from .ticket_commands import load_config
79
+
80
+ # Load configuration (respects project-specific config in cwd)
81
+ config = load_config()
82
+
83
+ # Determine adapter type with priority: CLI arg > config > .env files > default
84
+ if adapter:
85
+ # Priority 1: Command line argument
86
+ adapter_type = adapter
87
+ # Get base config from config file
88
+ adapters_config = config.get("adapters", {})
89
+ adapter_config = adapters_config.get(adapter_type, {})
90
+ else:
91
+ # Priority 2: Configuration file (project-specific)
92
+ adapter_type = config.get("default_adapter")
93
+ if adapter_type:
94
+ adapters_config = config.get("adapters", {})
95
+ adapter_config = adapters_config.get(adapter_type, {})
96
+ else:
97
+ # Priority 3: .env files (auto-detection fallback)
98
+ from ..mcp.server.main import _load_env_configuration
99
+
100
+ env_config = _load_env_configuration()
101
+ if env_config:
102
+ adapter_type = env_config["adapter_type"]
103
+ adapter_config = env_config["adapter_config"]
104
+ else:
105
+ # Priority 4: Default fallback
106
+ adapter_type = "aitrackdown"
107
+ adapters_config = config.get("adapters", {})
108
+ adapter_config = adapters_config.get(adapter_type, {})
109
+
110
+ # Override with command line options if provided (highest priority)
111
+ if base_path and adapter_type == "aitrackdown":
112
+ adapter_config["base_path"] = base_path
113
+
114
+ # Fallback to legacy config format
115
+ if not adapter_config and "config" in config:
116
+ adapter_config = config["config"]
117
+
118
+ # MCP server uses stdio for JSON-RPC, so we can't print to stdout
119
+ # Only print to stderr to avoid interfering with the protocol
120
+ if sys.stderr.isatty():
121
+ # Only print if stderr is a terminal (not redirected)
122
+ console.file = sys.stderr
123
+ console.print(
124
+ f"[green]Starting MCP SDK server[/green] with {adapter_type} adapter"
125
+ )
126
+ console.print(
127
+ "[dim]Server running on stdio. Send JSON-RPC requests via stdin.[/dim]"
128
+ )
129
+
130
+ # Configure adapter and run SDK server
131
+ try:
132
+ configure_adapter(adapter_type, adapter_config)
133
+ sdk_main()
134
+ except KeyboardInterrupt:
135
+ # Send this to stderr
136
+ if sys.stderr.isatty():
137
+ console.print("\n[yellow]Server stopped by user[/yellow]")
138
+ sys.exit(0)
139
+ except Exception as e:
140
+ # Log error to stderr
141
+ sys.stderr.write(f"MCP server error: {e}\n")
142
+ sys.exit(1)
143
+
144
+
145
+ @mcp_app.command(name="claude")
146
+ def mcp_claude(
147
+ global_config: bool = typer.Option(
148
+ False,
149
+ "--global",
150
+ "-g",
151
+ help="Configure Claude Desktop instead of project-level",
152
+ ),
153
+ force: bool = typer.Option(
154
+ False, "--force", "-f", help="Overwrite existing configuration"
155
+ ),
156
+ ) -> None:
157
+ """Configure Claude Code to use mcp-ticketer MCP server.
158
+
159
+ Reads configuration from .mcp-ticketer/config.json and updates
160
+ Claude Code's MCP settings accordingly.
161
+
162
+ By default, configures project-level (.mcp/config.json).
163
+ Use --global to configure Claude Desktop instead.
164
+
165
+ Examples:
166
+ --------
167
+ # Configure for current project (default)
168
+ mcp-ticketer mcp claude
169
+
170
+ # Configure Claude Desktop globally
171
+ mcp-ticketer mcp claude --global
172
+
173
+ # Force overwrite existing configuration
174
+ mcp-ticketer mcp claude --force
175
+
176
+ """
177
+ from ..cli.mcp_configure import configure_claude_mcp
178
+
179
+ try:
180
+ configure_claude_mcp(global_config=global_config, force=force)
181
+ except Exception as e:
182
+ console.print(f"[red]✗ Configuration failed:[/red] {e}")
183
+ raise typer.Exit(1) from e
184
+
185
+
186
+ @mcp_app.command(name="gemini")
187
+ def mcp_gemini(
188
+ scope: str = typer.Option(
189
+ "project",
190
+ "--scope",
191
+ "-s",
192
+ help="Configuration scope: 'project' (default) or 'user'",
193
+ ),
194
+ force: bool = typer.Option(
195
+ False, "--force", "-f", help="Overwrite existing configuration"
196
+ ),
197
+ ) -> None:
198
+ """Configure Gemini CLI to use mcp-ticketer MCP server.
199
+
200
+ Reads configuration from .mcp-ticketer/config.json and creates
201
+ Gemini CLI settings file with mcp-ticketer configuration.
202
+
203
+ By default, configures project-level (.gemini/settings.json).
204
+ Use --scope user to configure user-level (~/.gemini/settings.json).
205
+
206
+ Examples:
207
+ --------
208
+ # Configure for current project (default)
209
+ mcp-ticketer mcp gemini
210
+
211
+ # Configure at user level
212
+ mcp-ticketer mcp gemini --scope user
213
+
214
+ # Force overwrite existing configuration
215
+ mcp-ticketer mcp gemini --force
216
+
217
+ """
218
+ from ..cli.gemini_configure import configure_gemini_mcp
219
+
220
+ # Validate scope parameter
221
+ if scope not in ["project", "user"]:
222
+ console.print(
223
+ f"[red]✗ Invalid scope:[/red] '{scope}'. Must be 'project' or 'user'"
224
+ )
225
+ raise typer.Exit(1) from None
226
+
227
+ try:
228
+ configure_gemini_mcp(scope=scope, force=force) # type: ignore
229
+ except Exception as e:
230
+ console.print(f"[red]✗ Configuration failed:[/red] {e}")
231
+ raise typer.Exit(1) from e
232
+
233
+
234
+ @mcp_app.command(name="codex")
235
+ def mcp_codex(
236
+ force: bool = typer.Option(
237
+ False, "--force", "-f", help="Overwrite existing configuration"
238
+ ),
239
+ ) -> None:
240
+ """Configure Codex CLI to use mcp-ticketer MCP server.
241
+
242
+ Reads configuration from .mcp-ticketer/config.json and creates
243
+ Codex CLI config.toml with mcp-ticketer configuration.
244
+
245
+ IMPORTANT: Codex CLI ONLY supports global configuration at ~/.codex/config.toml.
246
+ There is no project-level configuration support. After configuration,
247
+ you must restart Codex CLI for changes to take effect.
248
+
249
+ Examples:
250
+ --------
251
+ # Configure Codex CLI globally
252
+ mcp-ticketer mcp codex
253
+
254
+ # Force overwrite existing configuration
255
+ mcp-ticketer mcp codex --force
256
+
257
+ """
258
+ from ..cli.codex_configure import configure_codex_mcp
259
+
260
+ try:
261
+ configure_codex_mcp(force=force)
262
+ except Exception as e:
263
+ console.print(f"[red]✗ Configuration failed:[/red] {e}")
264
+ raise typer.Exit(1) from e
265
+
266
+
267
+ @mcp_app.command(name="auggie")
268
+ def mcp_auggie(
269
+ force: bool = typer.Option(
270
+ False, "--force", "-f", help="Overwrite existing configuration"
271
+ ),
272
+ ) -> None:
273
+ """Configure Auggie CLI to use mcp-ticketer MCP server.
274
+
275
+ Reads configuration from .mcp-ticketer/config.json and creates
276
+ Auggie CLI settings.json with mcp-ticketer configuration.
277
+
278
+ IMPORTANT: Auggie CLI ONLY supports global configuration at ~/.augment/settings.json.
279
+ There is no project-level configuration support. After configuration,
280
+ you must restart Auggie CLI for changes to take effect.
281
+
282
+ Examples:
283
+ --------
284
+ # Configure Auggie CLI globally
285
+ mcp-ticketer mcp auggie
286
+
287
+ # Force overwrite existing configuration
288
+ mcp-ticketer mcp auggie --force
289
+
290
+ """
291
+ from ..cli.auggie_configure import configure_auggie_mcp
292
+
293
+ try:
294
+ configure_auggie_mcp(force=force)
295
+ except Exception as e:
296
+ console.print(f"[red]✗ Configuration failed:[/red] {e}")
297
+ raise typer.Exit(1) from e
298
+
299
+
300
+ @mcp_app.command(name="status")
301
+ def mcp_status() -> None:
302
+ """Check MCP server status.
303
+
304
+ Shows whether the MCP server is configured and running for various platforms.
305
+
306
+ Examples:
307
+ --------
308
+ mcp-ticketer mcp status
309
+
310
+ """
311
+ console.print("[bold]MCP Server Status[/bold]\n")
312
+
313
+ # Check project-level configuration
314
+ project_config = Path.cwd() / ".mcp-ticketer" / "config.json"
315
+ if project_config.exists():
316
+ console.print(f"[green]✓[/green] Project config found: {project_config}")
317
+ try:
318
+ with open(project_config) as f:
319
+ config = json.load(f)
320
+ adapter = config.get("default_adapter", "aitrackdown")
321
+ console.print(f" Default adapter: [cyan]{adapter}[/cyan]")
322
+ except Exception as e:
323
+ console.print(f" [yellow]Warning: Could not read config: {e}[/yellow]")
324
+ else:
325
+ console.print("[yellow]○[/yellow] No project config found")
326
+
327
+ # Check Claude Code configuration
328
+ claude_code_config = Path.cwd() / ".mcp" / "config.json"
329
+ if claude_code_config.exists():
330
+ console.print(
331
+ f"\n[green]✓[/green] Claude Code configured: {claude_code_config}"
332
+ )
333
+ else:
334
+ console.print("\n[yellow]○[/yellow] Claude Code not configured")
335
+
336
+ # Check Claude Desktop configuration
337
+ claude_desktop_config = (
338
+ Path.home()
339
+ / "Library"
340
+ / "Application Support"
341
+ / "Claude"
342
+ / "claude_desktop_config.json"
343
+ )
344
+ if claude_desktop_config.exists():
345
+ try:
346
+ with open(claude_desktop_config) as f:
347
+ config = json.load(f)
348
+ if "mcpServers" in config and "mcp-ticketer" in config["mcpServers"]:
349
+ console.print(
350
+ f"[green]✓[/green] Claude Desktop configured: {claude_desktop_config}"
351
+ )
352
+ else:
353
+ console.print(
354
+ "[yellow]○[/yellow] Claude Desktop config exists but mcp-ticketer not found"
355
+ )
356
+ except Exception:
357
+ console.print(
358
+ "[yellow]○[/yellow] Claude Desktop config exists but could not be read"
359
+ )
360
+ else:
361
+ console.print("[yellow]○[/yellow] Claude Desktop not configured")
362
+
363
+ # Check Gemini configuration
364
+ gemini_project_config = Path.cwd() / ".gemini" / "settings.json"
365
+ gemini_user_config = Path.home() / ".gemini" / "settings.json"
366
+ if gemini_project_config.exists():
367
+ console.print(
368
+ f"\n[green]✓[/green] Gemini (project) configured: {gemini_project_config}"
369
+ )
370
+ elif gemini_user_config.exists():
371
+ console.print(
372
+ f"\n[green]✓[/green] Gemini (user) configured: {gemini_user_config}"
373
+ )
374
+ else:
375
+ console.print("\n[yellow]○[/yellow] Gemini not configured")
376
+
377
+ # Check Codex configuration
378
+ codex_config = Path.home() / ".codex" / "config.toml"
379
+ if codex_config.exists():
380
+ console.print(f"[green]✓[/green] Codex configured: {codex_config}")
381
+ else:
382
+ console.print("[yellow]○[/yellow] Codex not configured")
383
+
384
+ # Check Auggie configuration
385
+ auggie_config = Path.home() / ".augment" / "settings.json"
386
+ if auggie_config.exists():
387
+ console.print(f"[green]✓[/green] Auggie configured: {auggie_config}")
388
+ else:
389
+ console.print("[yellow]○[/yellow] Auggie not configured")
390
+
391
+ console.print(
392
+ "\n[dim]Run 'mcp-ticketer install <platform>' to configure a platform[/dim]"
393
+ )
394
+
395
+
396
+ @mcp_app.command(name="stop")
397
+ def mcp_stop() -> None:
398
+ """Stop MCP server (placeholder - MCP runs on-demand via stdio).
399
+
400
+ Note: The MCP server runs on-demand when AI clients connect via stdio.
401
+ It doesn't run as a persistent background service, so there's nothing to stop.
402
+ This command is provided for consistency but has no effect.
403
+
404
+ Examples:
405
+ --------
406
+ mcp-ticketer mcp stop
407
+
408
+ """
409
+ console.print(
410
+ "[yellow]ℹ[/yellow] MCP server runs on-demand via stdio (not as a background service)"
411
+ )
412
+ console.print("There is no persistent server process to stop.")
413
+ console.print(
414
+ "\n[dim]The server starts automatically when AI clients connect and stops when they disconnect.[/dim]"
415
+ )
@@ -22,14 +22,17 @@ def migrate_config_command(dry_run: bool = False) -> None:
22
22
  """
23
23
  resolver = ConfigResolver()
24
24
 
25
+ # Get project config path (project-local only for security)
26
+ project_config_path = resolver.project_path / resolver.PROJECT_CONFIG_SUBPATH
27
+
25
28
  # Check if old config exists
26
- if not resolver.GLOBAL_CONFIG_PATH.exists():
29
+ if not project_config_path.exists():
27
30
  console.print("[yellow]No configuration found to migrate[/yellow]")
28
31
  return
29
32
 
30
33
  # Load old config
31
34
  try:
32
- with open(resolver.GLOBAL_CONFIG_PATH) as f:
35
+ with open(project_config_path) as f:
33
36
  old_config = json.load(f)
34
37
  except Exception as e:
35
38
  console.print(f"[red]Failed to load config: {e}[/red]")
@@ -71,22 +74,23 @@ def migrate_config_command(dry_run: bool = False) -> None:
71
74
  return
72
75
 
73
76
  # Backup old config
74
- backup_path = resolver.GLOBAL_CONFIG_PATH.with_suffix(".json.bak")
77
+ project_config_path = resolver.project_path / resolver.PROJECT_CONFIG_SUBPATH
78
+ backup_path = project_config_path.with_suffix(".json.bak")
75
79
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
76
- backup_path = resolver.GLOBAL_CONFIG_PATH.parent / f"config.{timestamp}.bak"
80
+ backup_path = project_config_path.parent / f"config.{timestamp}.bak"
77
81
 
78
82
  try:
79
- shutil.copy(resolver.GLOBAL_CONFIG_PATH, backup_path)
83
+ shutil.copy(project_config_path, backup_path)
80
84
  console.print(f"[green]✓[/green] Backed up old config to: {backup_path}")
81
85
  except Exception as e:
82
86
  console.print(f"[red]Failed to backup config: {e}[/red]")
83
87
  return
84
88
 
85
- # Save new config
89
+ # Save new config (to project-local config)
86
90
  try:
87
- resolver.save_global_config(new_config)
91
+ resolver.save_project_config(new_config)
88
92
  console.print("[green]✓[/green] Migration complete!")
89
- console.print(f"[dim]New config saved to: {resolver.GLOBAL_CONFIG_PATH}[/dim]")
93
+ console.print(f"[dim]New config saved to: {project_config_path}[/dim]")
90
94
  except Exception as e:
91
95
  console.print(f"[red]Failed to save new config: {e}[/red]")
92
96
  console.print(f"[yellow]Old config backed up at: {backup_path}[/yellow]")
@@ -24,7 +24,7 @@ jira_app = typer.Typer(
24
24
 
25
25
 
26
26
  @jira_app.command("projects")
27
- def jira_list_projects():
27
+ def jira_list_projects() -> None:
28
28
  """List JIRA projects (placeholder - not yet implemented)."""
29
29
  from rich.console import Console
30
30
 
@@ -38,7 +38,7 @@ def jira_list_projects():
38
38
 
39
39
 
40
40
  @jira_app.command("configure")
41
- def jira_configure():
41
+ def jira_configure() -> None:
42
42
  """Configure JIRA adapter (placeholder - not yet implemented)."""
43
43
  from rich.console import Console
44
44
 
@@ -55,7 +55,7 @@ github_app = typer.Typer(
55
55
 
56
56
 
57
57
  @github_app.command("repos")
58
- def github_list_repos():
58
+ def github_list_repos() -> None:
59
59
  """List GitHub repositories (placeholder - not yet implemented)."""
60
60
  from rich.console import Console
61
61
 
@@ -69,7 +69,7 @@ def github_list_repos():
69
69
 
70
70
 
71
71
  @github_app.command("configure")
72
- def github_configure():
72
+ def github_configure() -> None:
73
73
  """Configure GitHub adapter (placeholder - not yet implemented)."""
74
74
  from rich.console import Console
75
75
 
@@ -88,7 +88,7 @@ aitrackdown_app = typer.Typer(
88
88
 
89
89
 
90
90
  @aitrackdown_app.command("info")
91
- def aitrackdown_info():
91
+ def aitrackdown_info() -> None:
92
92
  """Show AITrackdown storage information (placeholder - not yet implemented)."""
93
93
  from rich.console import Console
94
94
 
@@ -104,7 +104,7 @@ def aitrackdown_info():
104
104
 
105
105
 
106
106
  @aitrackdown_app.command("configure")
107
- def aitrackdown_configure():
107
+ def aitrackdown_configure() -> None:
108
108
  """Configure AITrackdown adapter (placeholder - not yet implemented)."""
109
109
  from rich.console import Console
110
110