specfact-cli 0.4.0__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 specfact-cli might be problematic. Click here for more details.

Files changed (60) hide show
  1. specfact_cli/__init__.py +14 -0
  2. specfact_cli/agents/__init__.py +23 -0
  3. specfact_cli/agents/analyze_agent.py +392 -0
  4. specfact_cli/agents/base.py +95 -0
  5. specfact_cli/agents/plan_agent.py +202 -0
  6. specfact_cli/agents/registry.py +176 -0
  7. specfact_cli/agents/sync_agent.py +133 -0
  8. specfact_cli/analyzers/__init__.py +10 -0
  9. specfact_cli/analyzers/code_analyzer.py +775 -0
  10. specfact_cli/cli.py +397 -0
  11. specfact_cli/commands/__init__.py +7 -0
  12. specfact_cli/commands/enforce.py +87 -0
  13. specfact_cli/commands/import_cmd.py +355 -0
  14. specfact_cli/commands/init.py +119 -0
  15. specfact_cli/commands/plan.py +1090 -0
  16. specfact_cli/commands/repro.py +172 -0
  17. specfact_cli/commands/sync.py +408 -0
  18. specfact_cli/common/__init__.py +24 -0
  19. specfact_cli/common/logger_setup.py +673 -0
  20. specfact_cli/common/logging_utils.py +41 -0
  21. specfact_cli/common/text_utils.py +52 -0
  22. specfact_cli/common/utils.py +48 -0
  23. specfact_cli/comparators/__init__.py +10 -0
  24. specfact_cli/comparators/plan_comparator.py +391 -0
  25. specfact_cli/generators/__init__.py +13 -0
  26. specfact_cli/generators/plan_generator.py +105 -0
  27. specfact_cli/generators/protocol_generator.py +115 -0
  28. specfact_cli/generators/report_generator.py +200 -0
  29. specfact_cli/generators/workflow_generator.py +111 -0
  30. specfact_cli/importers/__init__.py +6 -0
  31. specfact_cli/importers/speckit_converter.py +773 -0
  32. specfact_cli/importers/speckit_scanner.py +704 -0
  33. specfact_cli/models/__init__.py +32 -0
  34. specfact_cli/models/deviation.py +105 -0
  35. specfact_cli/models/enforcement.py +150 -0
  36. specfact_cli/models/plan.py +97 -0
  37. specfact_cli/models/protocol.py +28 -0
  38. specfact_cli/modes/__init__.py +18 -0
  39. specfact_cli/modes/detector.py +126 -0
  40. specfact_cli/modes/router.py +153 -0
  41. specfact_cli/sync/__init__.py +11 -0
  42. specfact_cli/sync/repository_sync.py +279 -0
  43. specfact_cli/sync/speckit_sync.py +388 -0
  44. specfact_cli/utils/__init__.py +57 -0
  45. specfact_cli/utils/console.py +69 -0
  46. specfact_cli/utils/feature_keys.py +213 -0
  47. specfact_cli/utils/git.py +241 -0
  48. specfact_cli/utils/ide_setup.py +381 -0
  49. specfact_cli/utils/prompts.py +179 -0
  50. specfact_cli/utils/structure.py +496 -0
  51. specfact_cli/utils/yaml_utils.py +200 -0
  52. specfact_cli/validators/__init__.py +19 -0
  53. specfact_cli/validators/fsm.py +260 -0
  54. specfact_cli/validators/repro_checker.py +320 -0
  55. specfact_cli/validators/schema.py +200 -0
  56. specfact_cli-0.4.0.dist-info/METADATA +332 -0
  57. specfact_cli-0.4.0.dist-info/RECORD +60 -0
  58. specfact_cli-0.4.0.dist-info/WHEEL +4 -0
  59. specfact_cli-0.4.0.dist-info/entry_points.txt +2 -0
  60. specfact_cli-0.4.0.dist-info/licenses/LICENSE.md +55 -0
@@ -0,0 +1,355 @@
1
+ """
2
+ Import command - Import codebases and Spec-Kit projects to contract-driven format.
3
+
4
+ This module provides commands for importing existing codebases (brownfield) and
5
+ Spec-Kit projects and converting them to SpecFact contract-driven format.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+ from typing import Optional
12
+
13
+ import typer
14
+ from beartype import beartype
15
+ from icontract import ensure, require
16
+ from rich.console import Console
17
+ from rich.progress import Progress, SpinnerColumn, TextColumn
18
+
19
+ app = typer.Typer(help="Import codebases and Spec-Kit projects to contract format")
20
+ console = Console()
21
+
22
+
23
+ @app.command("from-spec-kit")
24
+ def from_spec_kit(
25
+ repo: Path = typer.Option(
26
+ Path("."),
27
+ "--repo",
28
+ help="Path to Spec-Kit repository",
29
+ exists=True,
30
+ file_okay=False,
31
+ dir_okay=True,
32
+ ),
33
+ dry_run: bool = typer.Option(
34
+ False,
35
+ "--dry-run",
36
+ help="Preview changes without writing files",
37
+ ),
38
+ write: bool = typer.Option(
39
+ False,
40
+ "--write",
41
+ help="Write changes to disk",
42
+ ),
43
+ out_branch: str = typer.Option(
44
+ "feat/specfact-migration",
45
+ "--out-branch",
46
+ help="Feature branch name for migration",
47
+ ),
48
+ report: Optional[Path] = typer.Option(
49
+ None,
50
+ "--report",
51
+ help="Path to write import report",
52
+ ),
53
+ force: bool = typer.Option(
54
+ False,
55
+ "--force",
56
+ help="Overwrite existing files",
57
+ ),
58
+ ) -> None:
59
+ """
60
+ Convert Spec-Kit project to SpecFact contract format.
61
+
62
+ This command scans a Spec-Kit repository, parses its structure,
63
+ and generates equivalent SpecFact contracts, protocols, and plans.
64
+
65
+ Example:
66
+ specfact import from-spec-kit --repo ./my-project --write
67
+ """
68
+ from specfact_cli.importers.speckit_converter import SpecKitConverter
69
+ from specfact_cli.importers.speckit_scanner import SpecKitScanner
70
+ from specfact_cli.utils.structure import SpecFactStructure
71
+
72
+ console.print(f"[bold cyan]Importing Spec-Kit project from:[/bold cyan] {repo}")
73
+
74
+ # Scan Spec-Kit structure
75
+ scanner = SpecKitScanner(repo)
76
+
77
+ if not scanner.is_speckit_repo():
78
+ console.print("[bold red]✗[/bold red] Not a Spec-Kit repository")
79
+ console.print("[dim]Expected: .specify/ directory[/dim]")
80
+ raise typer.Exit(1)
81
+
82
+ structure = scanner.scan_structure()
83
+
84
+ if dry_run:
85
+ console.print("[yellow]→ Dry run mode - no files will be written[/yellow]")
86
+ console.print("\n[bold]Detected Structure:[/bold]")
87
+ console.print(f" - Specs Directory: {structure.get('specs_dir', 'Not found')}")
88
+ console.print(f" - Memory Directory: {structure.get('specify_memory_dir', 'Not found')}")
89
+ if structure.get("feature_dirs"):
90
+ console.print(f" - Features Found: {len(structure['feature_dirs'])}")
91
+ if structure.get("memory_files"):
92
+ console.print(f" - Memory Files: {len(structure['memory_files'])}")
93
+ return
94
+
95
+ if not write:
96
+ console.print("[yellow]→ Use --write to actually convert files[/yellow]")
97
+ console.print("[dim]Use --dry-run to preview changes[/dim]")
98
+ return
99
+
100
+ # Ensure SpecFact structure exists
101
+ SpecFactStructure.ensure_structure(repo)
102
+
103
+ with Progress(
104
+ SpinnerColumn(),
105
+ TextColumn("[progress.description]{task.description}"),
106
+ console=console,
107
+ ) as progress:
108
+ # Step 1: Discover features from markdown artifacts
109
+ task = progress.add_task("Discovering Spec-Kit features...", total=None)
110
+ features = scanner.discover_features()
111
+ if not features:
112
+ console.print("[bold red]✗[/bold red] No features found in Spec-Kit repository")
113
+ console.print("[dim]Expected: specs/*/spec.md files[/dim]")
114
+ raise typer.Exit(1)
115
+ progress.update(task, description=f"✓ Discovered {len(features)} features")
116
+
117
+ # Step 2: Convert protocol
118
+ task = progress.add_task("Converting protocol...", total=None)
119
+ converter = SpecKitConverter(repo)
120
+ protocol = None
121
+ plan_bundle = None
122
+ try:
123
+ protocol = converter.convert_protocol()
124
+ progress.update(task, description=f"✓ Protocol converted ({len(protocol.states)} states)")
125
+
126
+ # Step 3: Convert plan
127
+ task = progress.add_task("Converting plan bundle...", total=None)
128
+ plan_bundle = converter.convert_plan()
129
+ progress.update(task, description=f"✓ Plan converted ({len(plan_bundle.features)} features)")
130
+
131
+ # Step 4: Generate Semgrep rules
132
+ task = progress.add_task("Generating Semgrep rules...", total=None)
133
+ semgrep_path = converter.generate_semgrep_rules()
134
+ progress.update(task, description="✓ Semgrep rules generated")
135
+
136
+ # Step 5: Generate GitHub Action workflow
137
+ task = progress.add_task("Generating GitHub Action workflow...", total=None)
138
+ repo_name = repo.name if isinstance(repo, Path) else None
139
+ workflow_path = converter.generate_github_action(repo_name=repo_name)
140
+ progress.update(task, description="✓ GitHub Action workflow generated")
141
+
142
+ except Exception as e:
143
+ console.print(f"[bold red]✗[/bold red] Conversion failed: {e}")
144
+ raise typer.Exit(1) from e
145
+
146
+ # Generate report
147
+ if report and protocol and plan_bundle:
148
+ report_content = f"""# Spec-Kit Import Report
149
+
150
+ ## Repository: {repo}
151
+
152
+ ## Summary
153
+ - **States Found**: {len(protocol.states)}
154
+ - **Transitions**: {len(protocol.transitions)}
155
+ - **Features Extracted**: {len(plan_bundle.features)}
156
+ - **Total Stories**: {sum(len(f.stories) for f in plan_bundle.features)}
157
+
158
+ ## Generated Files
159
+ - **Protocol**: `.specfact/protocols/workflow.protocol.yaml`
160
+ - **Plan Bundle**: `.specfact/plans/main.bundle.yaml`
161
+ - **Semgrep Rules**: `.semgrep/async-anti-patterns.yml`
162
+ - **GitHub Action**: `.github/workflows/specfact-gate.yml`
163
+
164
+ ## States
165
+ {chr(10).join(f"- {state}" for state in protocol.states)}
166
+
167
+ ## Features
168
+ {chr(10).join(f"- {f.title} ({f.key})" for f in plan_bundle.features)}
169
+ """
170
+ report.parent.mkdir(parents=True, exist_ok=True)
171
+ report.write_text(report_content, encoding="utf-8")
172
+ console.print(f"[dim]Report written to: {report}[/dim]")
173
+
174
+ console.print("[bold green]✓[/bold green] Import complete!")
175
+ console.print("[dim]Protocol: .specfact/protocols/workflow.protocol.yaml[/dim]")
176
+ console.print("[dim]Plan: .specfact/plans/main.bundle.yaml[/dim]")
177
+ console.print("[dim]Semgrep Rules: .semgrep/async-anti-patterns.yml[/dim]")
178
+ console.print("[dim]GitHub Action: .github/workflows/specfact-gate.yml[/dim]")
179
+
180
+
181
+ @app.command("from-code")
182
+ @require(lambda repo: repo.exists() and repo.is_dir(), "Repo path must exist and be directory")
183
+ @require(lambda confidence: 0.0 <= confidence <= 1.0, "Confidence must be 0.0-1.0")
184
+ @ensure(lambda out: out is None or out.exists(), "Output path must exist if provided")
185
+ @beartype
186
+ def from_code(
187
+ repo: Path = typer.Option(
188
+ Path("."),
189
+ "--repo",
190
+ help="Path to repository to import",
191
+ exists=True,
192
+ file_okay=False,
193
+ dir_okay=True,
194
+ ),
195
+ name: Optional[str] = typer.Option(
196
+ None,
197
+ "--name",
198
+ help="Custom plan name (will be sanitized for filesystem, default: 'auto-derived')",
199
+ ),
200
+ out: Optional[Path] = typer.Option(
201
+ None,
202
+ "--out",
203
+ help="Output plan bundle path (default: .specfact/plans/<name>-<timestamp>.bundle.yaml)",
204
+ ),
205
+ shadow_only: bool = typer.Option(
206
+ False,
207
+ "--shadow-only",
208
+ help="Shadow mode - observe without enforcing",
209
+ ),
210
+ report: Optional[Path] = typer.Option(
211
+ None,
212
+ "--report",
213
+ help="Path to write analysis report (default: .specfact/reports/brownfield/analysis-<timestamp>.md)",
214
+ ),
215
+ confidence: float = typer.Option(
216
+ 0.5,
217
+ "--confidence",
218
+ min=0.0,
219
+ max=1.0,
220
+ help="Minimum confidence score for features",
221
+ ),
222
+ key_format: str = typer.Option(
223
+ "classname",
224
+ "--key-format",
225
+ help="Feature key format: 'classname' (FEATURE-CLASSNAME) or 'sequential' (FEATURE-001)",
226
+ ),
227
+ ) -> None:
228
+ """
229
+ Import plan bundle from existing codebase (one-way import).
230
+
231
+ Analyzes code structure using AI-first semantic understanding or AST-based fallback
232
+ to generate a plan bundle that represents the current system.
233
+
234
+ Example:
235
+ specfact import from-code --repo . --out brownfield-plan.yaml
236
+ """
237
+ from specfact_cli.agents.analyze_agent import AnalyzeAgent
238
+ from specfact_cli.agents.registry import get_agent
239
+ from specfact_cli.cli import get_current_mode
240
+ from specfact_cli.modes import get_router
241
+
242
+ mode = get_current_mode()
243
+
244
+ # Route command based on mode
245
+ router = get_router()
246
+ routing_result = router.route("import from-code", mode, {"repo": str(repo), "confidence": confidence})
247
+
248
+ from specfact_cli.generators.plan_generator import PlanGenerator
249
+ from specfact_cli.utils.structure import SpecFactStructure
250
+ from specfact_cli.validators.schema import validate_plan_bundle
251
+
252
+ # Ensure .specfact structure exists in the repository being imported
253
+ SpecFactStructure.ensure_structure(repo)
254
+
255
+ # Use default paths if not specified (relative to repo)
256
+ if out is None:
257
+ out = SpecFactStructure.get_timestamped_brownfield_report(repo, name=name)
258
+
259
+ if report is None:
260
+ report = SpecFactStructure.get_brownfield_analysis_path(repo)
261
+
262
+ console.print(f"[bold cyan]Importing repository:[/bold cyan] {repo}")
263
+ console.print(f"[dim]Confidence threshold: {confidence}[/dim]")
264
+
265
+ if shadow_only:
266
+ console.print("[yellow]→ Shadow mode - observe without enforcement[/yellow]")
267
+
268
+ try:
269
+ # Use AI-first approach in CoPilot mode, fallback to AST in CI/CD mode
270
+ if routing_result.execution_mode == "agent":
271
+ console.print("[dim]Mode: CoPilot (AI-first import)[/dim]")
272
+ # Get agent for this command
273
+ agent = get_agent("import from-code")
274
+ if agent and isinstance(agent, AnalyzeAgent):
275
+ # Build context for agent
276
+ context = {
277
+ "workspace": str(repo),
278
+ "current_file": None, # TODO: Get from IDE in Phase 4.2+
279
+ "selection": None, # TODO: Get from IDE in Phase 4.2+
280
+ }
281
+ # Inject context (for future LLM integration)
282
+ _enhanced_context = agent.inject_context(context)
283
+ # Use AI-first import
284
+ console.print("\n[cyan]🤖 AI-powered import (semantic understanding)...[/cyan]")
285
+ plan_bundle = agent.analyze_codebase(repo, confidence=confidence, plan_name=name)
286
+ console.print("[green]✓[/green] AI import complete")
287
+ else:
288
+ # Fallback to AST if agent not available
289
+ console.print("[yellow]⚠ Agent not available, falling back to AST-based import[/yellow]")
290
+ from specfact_cli.analyzers.code_analyzer import CodeAnalyzer
291
+
292
+ console.print("\n[cyan]🔍 Importing Python files (AST-based fallback)...[/cyan]")
293
+ analyzer = CodeAnalyzer(repo, confidence_threshold=confidence, key_format=key_format, plan_name=name)
294
+ plan_bundle = analyzer.analyze()
295
+ else:
296
+ # CI/CD mode: use AST-based import (no LLM available)
297
+ console.print("[dim]Mode: CI/CD (AST-based import)[/dim]")
298
+ from specfact_cli.analyzers.code_analyzer import CodeAnalyzer
299
+
300
+ console.print("\n[cyan]🔍 Importing Python files...[/cyan]")
301
+ analyzer = CodeAnalyzer(repo, confidence_threshold=confidence, key_format=key_format, plan_name=name)
302
+ plan_bundle = analyzer.analyze()
303
+
304
+ console.print(f"[green]✓[/green] Found {len(plan_bundle.features)} features")
305
+ console.print(f"[green]✓[/green] Detected themes: {', '.join(plan_bundle.product.themes)}")
306
+
307
+ # Show summary
308
+ total_stories = sum(len(f.stories) for f in plan_bundle.features)
309
+ console.print(f"[green]✓[/green] Total stories: {total_stories}\n")
310
+
311
+ # Generate plan file
312
+ out.parent.mkdir(parents=True, exist_ok=True)
313
+ generator = PlanGenerator()
314
+ generator.generate(plan_bundle, out)
315
+
316
+ console.print("[bold green]✓ Import complete![/bold green]")
317
+ console.print(f"[dim]Plan bundle written to: {out}[/dim]")
318
+
319
+ # Validate generated plan
320
+ is_valid, error, _ = validate_plan_bundle(out)
321
+ if is_valid:
322
+ console.print("[green]✓ Plan validation passed[/green]")
323
+ else:
324
+ console.print(f"[yellow]⚠ Plan validation warning: {error}[/yellow]")
325
+
326
+ # Generate report
327
+ report_content = f"""# Brownfield Import Report
328
+
329
+ ## Repository: {repo}
330
+
331
+ ## Summary
332
+ - **Features Found**: {len(plan_bundle.features)}
333
+ - **Total Stories**: {total_stories}
334
+ - **Detected Themes**: {", ".join(plan_bundle.product.themes)}
335
+ - **Confidence Threshold**: {confidence}
336
+
337
+ ## Output Files
338
+ - **Plan Bundle**: `{out}`
339
+ - **Import Report**: `{report}`
340
+
341
+ ## Features
342
+
343
+ """
344
+ for feature in plan_bundle.features:
345
+ report_content += f"### {feature.title} ({feature.key})\n"
346
+ report_content += f"- **Stories**: {len(feature.stories)}\n"
347
+ report_content += f"- **Confidence**: {feature.confidence}\n"
348
+ report_content += f"- **Outcomes**: {', '.join(feature.outcomes)}\n\n"
349
+
350
+ report.write_text(report_content)
351
+ console.print(f"[dim]Report written to: {report}[/dim]")
352
+
353
+ except Exception as e:
354
+ console.print(f"[bold red]✗ Import failed:[/bold red] {e}")
355
+ raise typer.Exit(1) from e
@@ -0,0 +1,119 @@
1
+ """
2
+ Init command - Initialize SpecFact for IDE integration.
3
+
4
+ This module provides the `specfact init` command to copy prompt templates
5
+ to IDE-specific locations for slash command integration.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+
12
+ import typer
13
+ from beartype import beartype
14
+ from icontract import ensure, require
15
+ from rich.console import Console
16
+ from rich.panel import Panel
17
+
18
+ from specfact_cli.utils.ide_setup import IDE_CONFIG, copy_templates_to_ide, detect_ide
19
+
20
+ app = typer.Typer(help="Initialize SpecFact for IDE integration")
21
+ console = Console()
22
+
23
+
24
+ @app.callback(invoke_without_command=True)
25
+ @require(lambda ide: ide in IDE_CONFIG or ide == "auto", "IDE must be valid or 'auto'")
26
+ @require(lambda repo: repo.exists() and repo.is_dir(), "Repo path must exist and be directory")
27
+ @ensure(lambda result: result is None, "Command should return None")
28
+ @beartype
29
+ def init(
30
+ ide: str = typer.Option(
31
+ "auto",
32
+ "--ide",
33
+ help="IDE type (auto, cursor, vscode, copilot, claude, gemini, qwen, opencode, windsurf, kilocode, auggie, roo, codebuddy, amp, q)",
34
+ ),
35
+ repo: Path = typer.Option(
36
+ Path("."),
37
+ "--repo",
38
+ help="Repository path (default: current directory)",
39
+ exists=True,
40
+ file_okay=False,
41
+ dir_okay=True,
42
+ ),
43
+ force: bool = typer.Option(
44
+ False,
45
+ "--force",
46
+ help="Overwrite existing files",
47
+ ),
48
+ ) -> None:
49
+ """
50
+ Initialize SpecFact for IDE integration.
51
+
52
+ Copies prompt templates to IDE-specific locations so slash commands work.
53
+ This command detects the IDE type (or uses --ide flag) and copies
54
+ SpecFact prompt templates to the appropriate directory.
55
+
56
+ Examples:
57
+ specfact init # Auto-detect IDE
58
+ specfact init --ide cursor # Initialize for Cursor
59
+ specfact init --ide vscode --force # Overwrite existing files
60
+ specfact init --repo /path/to/repo --ide copilot
61
+ """
62
+ # Resolve repo path
63
+ repo_path = repo.resolve()
64
+
65
+ # Detect IDE
66
+ detected_ide = detect_ide(ide)
67
+ ide_config = IDE_CONFIG[detected_ide]
68
+ ide_name = ide_config["name"]
69
+
70
+ console.print()
71
+ console.print(Panel("[bold cyan]SpecFact IDE Setup[/bold cyan]", border_style="cyan"))
72
+ console.print(f"[cyan]Repository:[/cyan] {repo_path}")
73
+ console.print(f"[cyan]IDE:[/cyan] {ide_name} ({detected_ide})")
74
+ console.print()
75
+
76
+ # Find templates directory
77
+ # Try relative to project root first (for development)
78
+ templates_dir = repo_path / "resources" / "prompts"
79
+ if not templates_dir.exists():
80
+ # Try relative to installed package (for distribution)
81
+ import importlib.util
82
+
83
+ spec = importlib.util.find_spec("specfact_cli")
84
+ if spec and spec.origin:
85
+ package_dir = Path(spec.origin).parent.parent
86
+ templates_dir = package_dir / "resources" / "prompts"
87
+ if not templates_dir.exists():
88
+ # Fallback: try resources/prompts in project root
89
+ templates_dir = Path(__file__).parent.parent.parent.parent / "resources" / "prompts"
90
+
91
+ if not templates_dir.exists():
92
+ console.print(f"[red]Error:[/red] Templates directory not found: {templates_dir}")
93
+ console.print("[yellow]Expected location:[/yellow] resources/prompts/")
94
+ console.print("[yellow]Please ensure SpecFact is properly installed.[/yellow]")
95
+ raise typer.Exit(1)
96
+
97
+ console.print(f"[cyan]Templates:[/cyan] {templates_dir}")
98
+ console.print()
99
+
100
+ # Copy templates to IDE location
101
+ try:
102
+ copied_files, settings_path = copy_templates_to_ide(repo_path, detected_ide, templates_dir, force)
103
+
104
+ if not copied_files:
105
+ console.print("[yellow]No templates copied (all files already exist, use --force to overwrite)[/yellow]")
106
+ raise typer.Exit(0)
107
+
108
+ console.print()
109
+ console.print(Panel("[bold green]✓ Initialization Complete[/bold green]", border_style="green"))
110
+ console.print(f"[green]Copied {len(copied_files)} template(s) to {ide_config['folder']}[/green]")
111
+ if settings_path:
112
+ console.print(f"[green]Updated VS Code settings:[/green] {settings_path}")
113
+ console.print()
114
+ console.print("[dim]You can now use SpecFact slash commands in your IDE![/dim]")
115
+ console.print("[dim]Example: /specfact-import-from-code --repo . --confidence 0.7[/dim]")
116
+
117
+ except Exception as e:
118
+ console.print(f"[red]Error:[/red] Failed to initialize IDE integration: {e}")
119
+ raise typer.Exit(1) from e