specfact-cli 0.4.2__py3-none-any.whl → 0.6.8__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.
Files changed (66) hide show
  1. specfact_cli/__init__.py +1 -1
  2. specfact_cli/agents/analyze_agent.py +2 -3
  3. specfact_cli/analyzers/__init__.py +2 -1
  4. specfact_cli/analyzers/ambiguity_scanner.py +601 -0
  5. specfact_cli/analyzers/code_analyzer.py +462 -30
  6. specfact_cli/analyzers/constitution_evidence_extractor.py +491 -0
  7. specfact_cli/analyzers/contract_extractor.py +419 -0
  8. specfact_cli/analyzers/control_flow_analyzer.py +281 -0
  9. specfact_cli/analyzers/requirement_extractor.py +337 -0
  10. specfact_cli/analyzers/test_pattern_extractor.py +330 -0
  11. specfact_cli/cli.py +151 -206
  12. specfact_cli/commands/constitution.py +281 -0
  13. specfact_cli/commands/enforce.py +42 -34
  14. specfact_cli/commands/import_cmd.py +481 -152
  15. specfact_cli/commands/init.py +224 -55
  16. specfact_cli/commands/plan.py +2133 -547
  17. specfact_cli/commands/repro.py +100 -78
  18. specfact_cli/commands/sync.py +701 -186
  19. specfact_cli/enrichers/constitution_enricher.py +765 -0
  20. specfact_cli/enrichers/plan_enricher.py +294 -0
  21. specfact_cli/importers/speckit_converter.py +364 -48
  22. specfact_cli/importers/speckit_scanner.py +65 -0
  23. specfact_cli/models/plan.py +42 -0
  24. specfact_cli/resources/mappings/node-async.yaml +49 -0
  25. specfact_cli/resources/mappings/python-async.yaml +47 -0
  26. specfact_cli/resources/mappings/speckit-default.yaml +82 -0
  27. specfact_cli/resources/prompts/specfact-enforce.md +185 -0
  28. specfact_cli/resources/prompts/specfact-import-from-code.md +626 -0
  29. specfact_cli/resources/prompts/specfact-plan-add-feature.md +188 -0
  30. specfact_cli/resources/prompts/specfact-plan-add-story.md +212 -0
  31. specfact_cli/resources/prompts/specfact-plan-compare.md +571 -0
  32. specfact_cli/resources/prompts/specfact-plan-init.md +531 -0
  33. specfact_cli/resources/prompts/specfact-plan-promote.md +352 -0
  34. specfact_cli/resources/prompts/specfact-plan-review.md +1276 -0
  35. specfact_cli/resources/prompts/specfact-plan-select.md +401 -0
  36. specfact_cli/resources/prompts/specfact-plan-update-feature.md +242 -0
  37. specfact_cli/resources/prompts/specfact-plan-update-idea.md +211 -0
  38. specfact_cli/resources/prompts/specfact-repro.md +268 -0
  39. specfact_cli/resources/prompts/specfact-sync.md +497 -0
  40. specfact_cli/resources/schemas/deviation.schema.json +61 -0
  41. specfact_cli/resources/schemas/plan.schema.json +204 -0
  42. specfact_cli/resources/schemas/protocol.schema.json +53 -0
  43. specfact_cli/resources/templates/github-action.yml.j2 +140 -0
  44. specfact_cli/resources/templates/plan.bundle.yaml.j2 +141 -0
  45. specfact_cli/resources/templates/pr-template.md.j2 +58 -0
  46. specfact_cli/resources/templates/protocol.yaml.j2 +24 -0
  47. specfact_cli/resources/templates/telemetry.yaml.example +35 -0
  48. specfact_cli/sync/__init__.py +10 -1
  49. specfact_cli/sync/watcher.py +268 -0
  50. specfact_cli/telemetry.py +440 -0
  51. specfact_cli/utils/acceptance_criteria.py +127 -0
  52. specfact_cli/utils/enrichment_parser.py +445 -0
  53. specfact_cli/utils/feature_keys.py +12 -3
  54. specfact_cli/utils/ide_setup.py +170 -0
  55. specfact_cli/utils/structure.py +179 -2
  56. specfact_cli/utils/yaml_utils.py +33 -0
  57. specfact_cli/validators/repro_checker.py +22 -1
  58. specfact_cli/validators/schema.py +15 -4
  59. specfact_cli-0.6.8.dist-info/METADATA +456 -0
  60. specfact_cli-0.6.8.dist-info/RECORD +99 -0
  61. {specfact_cli-0.4.2.dist-info → specfact_cli-0.6.8.dist-info}/entry_points.txt +1 -0
  62. specfact_cli-0.6.8.dist-info/licenses/LICENSE.md +202 -0
  63. specfact_cli-0.4.2.dist-info/METADATA +0 -370
  64. specfact_cli-0.4.2.dist-info/RECORD +0 -62
  65. specfact_cli-0.4.2.dist-info/licenses/LICENSE.md +0 -61
  66. {specfact_cli-0.4.2.dist-info → specfact_cli-0.6.8.dist-info}/WHEEL +0 -0
@@ -7,6 +7,7 @@ to IDE-specific locations for slash command integration.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import sys
10
11
  from pathlib import Path
11
12
 
12
13
  import typer
@@ -15,7 +16,14 @@ from icontract import ensure, require
15
16
  from rich.console import Console
16
17
  from rich.panel import Panel
17
18
 
18
- from specfact_cli.utils.ide_setup import IDE_CONFIG, copy_templates_to_ide, detect_ide
19
+ from specfact_cli.telemetry import telemetry
20
+ from specfact_cli.utils.ide_setup import (
21
+ IDE_CONFIG,
22
+ copy_templates_to_ide,
23
+ detect_ide,
24
+ find_package_resources_path,
25
+ get_package_installation_locations,
26
+ )
19
27
 
20
28
 
21
29
  app = typer.Typer(help="Initialize SpecFact for IDE integration")
@@ -65,61 +73,222 @@ def init(
65
73
  specfact init --ide vscode --force # Overwrite existing files
66
74
  specfact init --repo /path/to/repo --ide copilot
67
75
  """
68
- # Resolve repo path
69
- repo_path = repo.resolve()
70
-
71
- # Detect IDE
72
- detected_ide = detect_ide(ide)
73
- ide_config = IDE_CONFIG[detected_ide]
74
- ide_name = ide_config["name"]
75
-
76
- console.print()
77
- console.print(Panel("[bold cyan]SpecFact IDE Setup[/bold cyan]", border_style="cyan"))
78
- console.print(f"[cyan]Repository:[/cyan] {repo_path}")
79
- console.print(f"[cyan]IDE:[/cyan] {ide_name} ({detected_ide})")
80
- console.print()
81
-
82
- # Find templates directory
83
- # Try relative to project root first (for development)
84
- templates_dir = repo_path / "resources" / "prompts"
85
- if not templates_dir.exists():
86
- # Try relative to installed package (for distribution)
87
- import importlib.util
88
-
89
- spec = importlib.util.find_spec("specfact_cli")
90
- if spec and spec.origin:
91
- package_dir = Path(spec.origin).parent.parent
92
- templates_dir = package_dir / "resources" / "prompts"
93
- if not templates_dir.exists():
94
- # Fallback: try resources/prompts in project root
95
- templates_dir = Path(__file__).parent.parent.parent.parent / "resources" / "prompts"
96
-
97
- if not templates_dir.exists():
98
- console.print(f"[red]Error:[/red] Templates directory not found: {templates_dir}")
99
- console.print("[yellow]Expected location:[/yellow] resources/prompts/")
100
- console.print("[yellow]Please ensure SpecFact is properly installed.[/yellow]")
101
- raise typer.Exit(1)
102
-
103
- console.print(f"[cyan]Templates:[/cyan] {templates_dir}")
104
- console.print()
105
-
106
- # Copy templates to IDE location
107
- try:
108
- copied_files, settings_path = copy_templates_to_ide(repo_path, detected_ide, templates_dir, force)
109
-
110
- if not copied_files:
111
- console.print("[yellow]No templates copied (all files already exist, use --force to overwrite)[/yellow]")
112
- raise typer.Exit(0)
76
+ telemetry_metadata = {
77
+ "ide": ide,
78
+ "force": force,
79
+ }
80
+
81
+ with telemetry.track_command("init", telemetry_metadata) as record:
82
+ # Resolve repo path
83
+ repo_path = repo.resolve()
84
+
85
+ # Detect IDE
86
+ detected_ide = detect_ide(ide)
87
+ ide_config = IDE_CONFIG[detected_ide]
88
+ ide_name = ide_config["name"]
113
89
 
114
90
  console.print()
115
- console.print(Panel("[bold green] Initialization Complete[/bold green]", border_style="green"))
116
- console.print(f"[green]Copied {len(copied_files)} template(s) to {ide_config['folder']}[/green]")
117
- if settings_path:
118
- console.print(f"[green]Updated VS Code settings:[/green] {settings_path}")
91
+ console.print(Panel("[bold cyan]SpecFact IDE Setup[/bold cyan]", border_style="cyan"))
92
+ console.print(f"[cyan]Repository:[/cyan] {repo_path}")
93
+ console.print(f"[cyan]IDE:[/cyan] {ide_name} ({detected_ide})")
119
94
  console.print()
120
- console.print("[dim]You can now use SpecFact slash commands in your IDE![/dim]")
121
- console.print("[dim]Example: /specfact-import-from-code --repo . --confidence 0.7[/dim]")
122
95
 
123
- except Exception as e:
124
- console.print(f"[red]Error:[/red] Failed to initialize IDE integration: {e}")
125
- raise typer.Exit(1) from e
96
+ # Find templates directory
97
+ # Priority order:
98
+ # 1. Development: relative to project root (resources/prompts)
99
+ # 2. Installed package: use importlib.resources to find package location
100
+ # 3. Fallback: try relative to this file (for edge cases)
101
+ templates_dir: Path | None = None
102
+ package_templates_dir: Path | None = None
103
+ tried_locations: list[Path] = []
104
+
105
+ # Try 1: Development mode - relative to repo root
106
+ dev_templates_dir = (repo_path / "resources" / "prompts").resolve()
107
+ tried_locations.append(dev_templates_dir)
108
+ console.print(f"[dim]Debug:[/dim] Trying development path: {dev_templates_dir}")
109
+ if dev_templates_dir.exists():
110
+ templates_dir = dev_templates_dir
111
+ console.print(f"[green]✓[/green] Found templates at: {templates_dir}")
112
+ else:
113
+ console.print("[dim]Debug:[/dim] Development path not found, trying installed package...")
114
+ # Try 2: Installed package - use importlib.resources
115
+ # Note: importlib is part of Python's standard library (since Python 3.1)
116
+ # importlib.resources.files() is available since Python 3.9
117
+ # Since we require Python >=3.11, this should always be available
118
+ # However, we catch exceptions for robustness (minimal installations, edge cases)
119
+ package_templates_dir = None
120
+ try:
121
+ import importlib.resources
122
+
123
+ console.print("[dim]Debug:[/dim] Using importlib.resources.files() API...")
124
+ # Use files() API (Python 3.9+) - recommended approach
125
+ resources_ref = importlib.resources.files("specfact_cli")
126
+ templates_ref = resources_ref / "resources" / "prompts"
127
+ # Convert Traversable to Path
128
+ # Traversable objects can be converted to Path via str()
129
+ # Use resolve() to handle Windows/Linux/macOS path differences
130
+ package_templates_dir = Path(str(templates_ref)).resolve()
131
+ tried_locations.append(package_templates_dir)
132
+ console.print(f"[dim]Debug:[/dim] Package templates path: {package_templates_dir}")
133
+ if package_templates_dir.exists():
134
+ templates_dir = package_templates_dir
135
+ console.print(f"[green]✓[/green] Found templates at: {templates_dir}")
136
+ else:
137
+ console.print("[yellow]⚠[/yellow] Package templates path exists but directory not found")
138
+ except (ImportError, ModuleNotFoundError) as e:
139
+ console.print(
140
+ f"[yellow]⚠[/yellow] importlib.resources not available or module not found: {type(e).__name__}: {e}"
141
+ )
142
+ console.print("[dim]Debug:[/dim] Falling back to importlib.util.find_spec()...")
143
+ except (TypeError, AttributeError, ValueError) as e:
144
+ console.print(f"[yellow]⚠[/yellow] Error converting Traversable to Path: {e}")
145
+ console.print("[dim]Debug:[/dim] Falling back to importlib.util.find_spec()...")
146
+ except Exception as e:
147
+ console.print(f"[yellow]⚠[/yellow] Unexpected error with importlib.resources: {type(e).__name__}: {e}")
148
+ console.print("[dim]Debug:[/dim] Falling back to importlib.util.find_spec()...")
149
+
150
+ # Fallback: importlib.util.find_spec() + comprehensive package location search
151
+ if not templates_dir or not templates_dir.exists():
152
+ try:
153
+ import importlib.util
154
+
155
+ console.print("[dim]Debug:[/dim] Using importlib.util.find_spec() fallback...")
156
+ spec = importlib.util.find_spec("specfact_cli")
157
+ if spec and spec.origin:
158
+ # spec.origin points to __init__.py
159
+ # Go up to package root, then to resources/prompts
160
+ # Use resolve() for cross-platform compatibility
161
+ package_root = Path(spec.origin).parent.resolve()
162
+ package_templates_dir = (package_root / "resources" / "prompts").resolve()
163
+ tried_locations.append(package_templates_dir)
164
+ console.print(f"[dim]Debug:[/dim] Package root from spec.origin: {package_root}")
165
+ console.print(f"[dim]Debug:[/dim] Templates path from spec: {package_templates_dir}")
166
+ if package_templates_dir.exists():
167
+ templates_dir = package_templates_dir
168
+ console.print(f"[green]✓[/green] Found templates at: {templates_dir}")
169
+ else:
170
+ console.print("[yellow]⚠[/yellow] Templates path from spec not found")
171
+ else:
172
+ console.print("[yellow]⚠[/yellow] Could not find specfact_cli module spec")
173
+ if spec is None:
174
+ console.print("[dim]Debug:[/dim] spec is None")
175
+ elif not spec.origin:
176
+ console.print("[dim]Debug:[/dim] spec.origin is None or empty")
177
+ except Exception as e:
178
+ console.print(f"[yellow]⚠[/yellow] Error with importlib.util.find_spec(): {type(e).__name__}: {e}")
179
+
180
+ # Fallback: Comprehensive package location search (cross-platform)
181
+ if not templates_dir or not templates_dir.exists():
182
+ try:
183
+ console.print("[dim]Debug:[/dim] Searching all package installation locations...")
184
+ package_locations = get_package_installation_locations("specfact_cli")
185
+ console.print(f"[dim]Debug:[/dim] Found {len(package_locations)} possible package location(s)")
186
+ for i, loc in enumerate(package_locations, 1):
187
+ console.print(f"[dim]Debug:[/dim] {i}. {loc}")
188
+ # Check for resources/prompts in this package location
189
+ resource_path = (loc / "resources" / "prompts").resolve()
190
+ tried_locations.append(resource_path)
191
+ if resource_path.exists():
192
+ templates_dir = resource_path
193
+ console.print(f"[green]✓[/green] Found templates at: {templates_dir}")
194
+ break
195
+ if not templates_dir or not templates_dir.exists():
196
+ # Try using the helper function as a final attempt
197
+ console.print("[dim]Debug:[/dim] Trying find_package_resources_path() helper...")
198
+ resource_path = find_package_resources_path("specfact_cli", "resources/prompts")
199
+ if resource_path and resource_path.exists():
200
+ tried_locations.append(resource_path)
201
+ templates_dir = resource_path
202
+ console.print(f"[green]✓[/green] Found templates at: {templates_dir}")
203
+ else:
204
+ console.print("[yellow]⚠[/yellow] Resources not found in any package location")
205
+ except Exception as e:
206
+ console.print(f"[yellow]⚠[/yellow] Error searching package locations: {type(e).__name__}: {e}")
207
+
208
+ # Try 3: Fallback - relative to this file (for edge cases)
209
+ if not templates_dir or not templates_dir.exists():
210
+ try:
211
+ console.print("[dim]Debug:[/dim] Trying fallback: relative to __file__...")
212
+ # Get the directory containing this file (init.py)
213
+ # init.py is in: src/specfact_cli/commands/init.py
214
+ # Go up: commands -> specfact_cli -> src -> project root
215
+ current_file = Path(__file__).resolve()
216
+ fallback_dir = (current_file.parent.parent.parent.parent / "resources" / "prompts").resolve()
217
+ tried_locations.append(fallback_dir)
218
+ console.print(f"[dim]Debug:[/dim] Current file: {current_file}")
219
+ console.print(f"[dim]Debug:[/dim] Fallback templates path: {fallback_dir}")
220
+ if fallback_dir.exists():
221
+ templates_dir = fallback_dir
222
+ console.print(f"[green]✓[/green] Found templates at: {templates_dir}")
223
+ else:
224
+ console.print("[yellow]⚠[/yellow] Fallback path not found")
225
+ except Exception as e:
226
+ console.print(f"[yellow]⚠[/yellow] Error with __file__ fallback: {type(e).__name__}: {e}")
227
+
228
+ if not templates_dir or not templates_dir.exists():
229
+ console.print()
230
+ console.print("[red]Error:[/red] Templates directory not found after all attempts")
231
+ console.print()
232
+ console.print("[yellow]Tried locations:[/yellow]")
233
+ for i, location in enumerate(tried_locations, 1):
234
+ exists = "✓" if location.exists() else "✗"
235
+ console.print(f" {i}. {exists} {location}")
236
+ console.print()
237
+ console.print("[yellow]Debug information:[/yellow]")
238
+ console.print(f" - Python version: {sys.version}")
239
+ console.print(f" - Platform: {sys.platform}")
240
+ console.print(f" - Current working directory: {Path.cwd()}")
241
+ console.print(f" - Repository path: {repo_path}")
242
+ console.print(f" - __file__ location: {Path(__file__).resolve()}")
243
+ try:
244
+ import importlib.util
245
+
246
+ spec = importlib.util.find_spec("specfact_cli")
247
+ if spec:
248
+ console.print(f" - Module spec found: {spec}")
249
+ console.print(f" - Module origin: {spec.origin}")
250
+ if spec.origin:
251
+ console.print(f" - Module location: {Path(spec.origin).parent.resolve()}")
252
+ else:
253
+ console.print(" - Module spec: Not found")
254
+ except Exception as e:
255
+ console.print(f" - Error checking module spec: {e}")
256
+ console.print()
257
+ console.print("[yellow]Expected location:[/yellow] resources/prompts/")
258
+ console.print("[yellow]Please ensure SpecFact is properly installed.[/yellow]")
259
+ raise typer.Exit(1)
260
+
261
+ console.print(f"[cyan]Templates:[/cyan] {templates_dir}")
262
+ console.print()
263
+
264
+ # Copy templates to IDE location
265
+ try:
266
+ copied_files, settings_path = copy_templates_to_ide(repo_path, detected_ide, templates_dir, force)
267
+
268
+ if not copied_files:
269
+ console.print(
270
+ "[yellow]No templates copied (all files already exist, use --force to overwrite)[/yellow]"
271
+ )
272
+ record({"files_copied": 0, "already_exists": True})
273
+ raise typer.Exit(0)
274
+
275
+ record(
276
+ {
277
+ "detected_ide": detected_ide,
278
+ "files_copied": len(copied_files),
279
+ "settings_updated": settings_path is not None,
280
+ }
281
+ )
282
+
283
+ console.print()
284
+ console.print(Panel("[bold green]✓ Initialization Complete[/bold green]", border_style="green"))
285
+ console.print(f"[green]Copied {len(copied_files)} template(s) to {ide_config['folder']}[/green]")
286
+ if settings_path:
287
+ console.print(f"[green]Updated VS Code settings:[/green] {settings_path}")
288
+ console.print()
289
+ console.print("[dim]You can now use SpecFact slash commands in your IDE![/dim]")
290
+ console.print("[dim]Example: /specfact-import-from-code --repo . --confidence 0.7[/dim]")
291
+
292
+ except Exception as e:
293
+ console.print(f"[red]Error:[/red] Failed to initialize IDE integration: {e}")
294
+ raise typer.Exit(1) from e